描けてしまう、ということ
前回、8,989点を点群として描きました。軌跡はきれいに出ます。 おかしな点が混ざっていても、絵としては成立してしまうのです。
速度で色を分けたとき、経路の一部だけが不自然に赤くなっていました。 調べたら、そこは時速109kmで移動していることになっていた。 歩いて記録したデータです。
この記事では、そういう点を4種類ほど拾います。 いずれもデータが壊れているのではなく、そう記録されるのが正常な現象です。 壊れていないから、気づきにくい。
時速109kmで、歩いていた
累積距離の差分から速度を出すと、上位はこうなりました。
| 点番号 | 時刻 | Δ距離 | 速度 |
|---|---|---|---|
| #348 | 06:51:51 | 30.4 m | 109.5 km/h |
| #349 | 06:51:52 | 30.2 m | 108.9 km/h |
| #350 | 06:51:53 | 30.2 m | 108.5 km/h |
| #351 | 06:51:54 | 29.6 m | 106.5 km/h |
| #3 | 06:44:26 | 5.9 m | 21.1 km/h |
| #4520 | 08:01:24 | 3.6 m | 13.1 km/h |
4秒連続で、毎秒30mずつ進んでいます。合計120m。 走っても届きませんし、そもそも歩いていた記録です。
直前に、101秒の空白があった
生ログを前後まで広げると、事情が見えました。
#345 06:50:08 Δt= 1s Δd= 1.0m 3.5 km/h #346 06:50:09 Δt= 1s Δd= 1.3m 4.6 km/h #347 06:51:50 Δt=101s Δd= 30.7m 1.1 km/h ← 記録が飛んでいる #348 06:51:51 Δt= 1s Δd= 30.4m 109.5 km/h #349 06:51:52 Δt= 1s Δd= 30.2m 108.9 km/h #350 06:51:53 Δt= 1s Δd= 30.2m 108.5 km/h #351 06:51:54 Δt= 1s Δd= 29.6m 106.5 km/h #352 06:51:55 Δt= 1s Δd= 2.9m 10.4 km/h #353 06:51:56 Δt= 1s Δd= 1.3m 4.7 km/h #354 06:51:57 Δt= 1s Δd= 1.2m 4.1 km/h
#346 の 06:50:09 から #347 の 06:51:50 まで、点がありません。
信号を見失っていたのでしょう。そして復帰した直後、
4秒かけて位置を「取り戻して」いる。
飛びは、断絶の後始末でした。
3秒を超える断絶は、11km の記録でこの1箇所だけです。 逆に言えば、1度の信号ロストが4点の異常値を生みました。
どこから捨てるか
では外れ値をどう扱うか。閾値を決める必要がありますが、 「正常」との境目は連続しています。
| 閾値 | 除外される点 | 評価 |
|---|---|---|
| 15 km/h 超 | 5 点(0.056%) | 測位の飛びだけを狙える |
| 10 km/h 超 | 5 点 | 同上 |
| 8 km/h 超 | 数十 点 | 速歩きも巻き込む |
| 6 km/h 超 | 数百 点 | 普通の歩行が消える |
歩行速度の95パーセンタイルは 6.7 km/h、最大でも 13.1 km/h でした。 一方で飛びは 106〜109 km/h。その間がぽっかり空いています。 だから 15 km/h という切り方で、飛びだけを外せました。
閾値を、動かしてみる
実データです。除外する速度の閾値を変えると、 残る点数と積算距離がどう変わるか見てください。
93%が「複数回通過」に見えた
同じ道を何度通ったかを知りたくて、座標を格子に丸めて数えました。 緯度経度を小数第4位で切ると、約11m四方の格子になります。
const grid = {}; pts.forEach(p => { const key = p.lat.toFixed(4) + ',' + p.lon.toFixed(4); grid[key] = (grid[key] || 0) + 1; }); // 2点以上ある格子=「複数回通過した」とみなす
結果は、こうなりました。
| 指標 | 値 |
|---|---|
| 格子の総数 | 約 1,230 マス |
| 2点以上ある格子 | 93 % |
93%の格子を複数回通った——そんな経路ではありません。 始点と終点が5.8km離れた、ほぼ一本道の歩行です。
1マスに8秒いる
実際、8,989点 ÷ 約1,230マス = 約7.3点/マス。計算どおりです。 「複数回通過」ではなく、1回通過するのに複数点かかっているだけでした。
本当の再訪を数える
時間的に離れた訪問だけを数え直します。 同じ格子への点が60点(=60秒)以上空いて再登場したら、再訪とみなす。
| 数え方 | 該当する格子 |
|---|---|
| 2点以上ある(素朴) | 93 % |
| 時間的に離れた再訪 | 約 2.5 % |
93% が 2.5% になりました。 同じデータ、同じ格子です。変えたのは数え方だけ。
「止まっていた時間」も、決まらない
前回、停止した場所が点の密集として光る話を書きました。 では合計何分止まっていたのか——これも一意には決まりません。
| 停止とみなす速度 | 箇所数 | 最長 | 合計 |
|---|---|---|---|
| 0.3 km/h 未満 | 7 | 40 秒 | 2 分 |
| 0.5 km/h 未満 | 7 | 40 秒 | 2 分 |
| 1.0 km/h 未満 | 20 | 112 秒 | 13 分 |
| 1.5 km/h 未満 | 22 | 117 秒 | 15 分 |
| 2.0 km/h 未満 | 23 | 17 分 | 17 分 |
0.5 から 1.0 へ動かすだけで、7箇所2分が20箇所13分に変わります。 6倍以上です。
理由は分かります。人は完全には止まらない。 信号待ちでも体は揺れ、GPSはそれを 0.5〜1.0 km/h として拾います。 「止まっている」と「ゆっくり動いている」の間に、明確な線はありません。
坂で、心拍は跳ねなかった
標高データがあるので、勾配と心拍の関係を見たくなりました。 坂を上れば心拍が上がるはず——確かめるまでもない、と思っていました。
| 区間 | 平均心拍 | 点数 |
|---|---|---|
| 上り(勾配 > 2%) | 約 109 bpm | 約 1,700 |
| 平坦(−1% 〜 +1%) | 約 106 bpm | 約 4,600 |
| 下り(勾配 < −2%) | 約 105 bpm | 約 1,400 |
差は4 bpm ほど。方向は予想どおりですが、想像よりずっと小さい。 相関係数を計算すると r = 0.13(勾配の算出方法により 0.128〜0.132)でした。 ほぼ無相関です。
考えられる理由
| 要因 | 内容 |
|---|---|
| 心拍の遅れ | 坂を上り始めてから心拍が上がるまで数十秒かかる。同じ秒で対応づけると、ずれる |
| 勾配が小さい | 標高差36.2m。都市部の歩行では負荷として弱い |
| 標高の精度 | GPS由来の標高は誤差が大きく、1秒ごとの差分はノイズを多く含む |
| ペース調整 | 坂では自然に歩を緩める。負荷が一定に保たれる |
もうひとつの嘘 — float32
前回の実装で踏んだことも、 同じ種類の問題でした。
緯度経度を Float32Array で保持したところ、
積算距離が 10.99km から 11.57km に増えました。
float32 が緯度を表せる最小刻みは 0.425m。
歩行の1点間隔 1.2m に対して粗すぎたのです。
正常に記録された、おかしな値
ここで挙げたものは、どれも機器の故障ではありません。 信号が途切れれば復帰時に位置を補正しますし、 1Hzで記録すれば1マスに複数点が入ります。 人は完全には止まらないし、float32 の刻みは仕様どおりです。
正常に記録された値から、おかしな結論が出てくる。 だから「データが壊れていないか」ではなく、 「この数字はどう作られたか」を見る必要がありました。
この記事のまとめ
- 時速109kmの点が4つ。直前に101秒の記録断絶があり、飛びはその後始末だった。
- 速度は記録された値ではなく累積距離を時間で割った派生値。元の値より壊れやすい。
- 外れ値と正常値の間が空いていたので 15km/h で切れた。それはたまたま。
- 「93%の格子を複数回通過」は錯覚。1マスに8秒いるので、8点入るのが当たり前だった。
- 格子を粗くすると比率は上がる。解像度ではなく数え方の問題。
- 停止時間は閾値で2分にも13分にもなる。数字を出すなら閾値も書く。
- 坂と心拍の相関は r ≒ 0.13。差は4bpmほど。予想は外れた。理由は急いで作らない。
- float32 の刻み 0.425m が歩幅より粗く、距離が5%増えた。扱い方で値が変わる。
- 軌跡を、点群で描く
WebGPU で 8,989点を描く - GPSは、嘘をつく(この記事)
測位の飛び・集計の錯覚 - 同じ人が、違う道を歩くと
4ルートを並べて比べる