何が起きるか
日ごとのスコアが次のようになっていたとします。
| 日 | 結果 |
|---|---|
| 月 | 90 |
| 火 | データなし |
| 水 | 60 |
測定できた日だけで平均すると 75 です。火曜を 0 として計算すると 50 になり、グラフなどに反映させたときにも存在しない「0点」が描かれます。
ただし、本当に「測定した結果が0」だった日は平均に含める必要があります。つまり「0なら除外する」というルールでは解決しません。
「測定できたか」を値と一緒に持つ
データ元が未測定の日にも 0 を返す場合、数値だけでは区別できません。受け取った時点で、未測定を null に変換します。
data class DailyRaw(val value: Int, val measured: Boolean)
fun DailyRaw.toChartValue(): Int? = if (measured) value else null
平均は null を除いて計算する
val raws = listOf(
DailyRaw(90, measured = true),
DailyRaw(0, measured = false),
DailyRaw(60, measured = true),
)
val values = raws.map { it.toChartValue() } // [90, null, 60]
val average = values.filterNotNull()
.takeIf { it.isNotEmpty() }
?.average()
println(average) // 75.0
filterNotNull() で除かれるのは未測定の日だけです。測定済みの 0 は残ります。全日が未測定なら平均も null になるので、「平均0」と誤って表示することもありません。
参考:Kotlin 公式ドキュメント - Null safety
0埋めは表示の直前だけにする
欠測日に何かを表示したい場合は、表示のタイミングで変換します。
val label = value?.toString() ?: "-"
集計の前に null を 0 に変えてしまうと、測定済みの0と区別できなくなります。
// NG
val wrongAverage = values.map { it ?: 0 }.average() // 50.0
まとめ
- 「データなし」は
null、「測定結果が0」は0として分ける - 集計は
nullを除いて行い、0埋めは表示時だけにする - テストでは「正の値」「測定済みの0」「データなし」の3パターンを確認する
理解できている人には当たり前のことですが、実装しながらだと抜けやすい人もいると思います。
今回はそんな場合の時のために言語化して整理するための記事にしました。