Pola pembacaan

Pemindaian rentang, last-known-value, dan agregasi atas jendela waktu.

Daftar isi

Pembacaan data time-series didominasi oleh tiga pola, yaitu pemindaian rentang waktu, last-known-value per deret, dan agregasi atas jendela waktu. Topik ini membedah ketiga pola tersebut dengan ilustrasi pandas pada dataset telemetri satu hari penuh, lalu menunjukkan bagaimana masing-masing memetakan ke operasi indeks dan kompresi di sisi mesin. Setelah membaca topik ini, pilihan indeks dan strategi pemartisian menjadi lebih mudah dijustifikasi berdasarkan pola akses yang nyata.

Pemindaian rentang waktu#

Pola pembacaan yang paling lazim adalah pemindaian rentang, yaitu pengambilan seluruh titik yang cap waktunya berada dalam sebuah interval. Contoh klasiknya adalah “suhu 12 jam terakhir” atau “data metrik antara dua titik waktu”. Karena data time-series terurut menurut cap waktu, pemindaian rentang menjadi operasi yang dapat dilayani oleh satu seek ke indeks waktu, lalu pemindaian berurutan pada rentang tersebut. Jika tabel dipartisi menurut waktu, hanya satu atau beberapa partisi yang perlu disentuh, sehingga biaya tidak bergantung pada ukuran tabel penuh.

Pada pandas, operasi ini dinyatakan dengan df.loc[awal:akhir] setelah kolom ts dijadikan indeks. Pada SQL, padanannya adalah WHERE ts BETWEEN awal AND akhir. Kedua bentuk ini secara semantik setara, dan keduanya memanfaatkan keterurutan untuk memangkas rentang sebelum pemindaian baris benar-benar terjadi. Pola ini sangat berbeda dari query operasional yang memilih satu baris berdasarkan kunci primer, sebab di sini rentang, bukan titik, yang menjadi target.

Last-known-value per deret#

Pola kedua adalah pengambilan nilai terakhir yang diketahui untuk setiap deret. Pada dashboard pemantauan, pola ini menjadi tulang punggung tampilan status terkini, yaitu “suhu terakhir sensor A”, “beban CPU terakhir host B”, dan sebagainya. Operasi ini secara konseptual adalah agregasi MAX(ts) per deret, lalu penggabungan kembali untuk mengambil nilai pada cap waktu maksimum tersebut. Pada sistem dengan banyak deret, biaya pola ini bergantung pada ketersediaan indeks (sensor_id, ts DESC) yang memungkinkan pengambilan nilai terakhir tanpa memindai seluruh deret.

Pada pandas, padanannya adalah df.groupby("sensor_id").tail(1), yaitu pengelompokan berdasarkan identitas deret lalu pengambilan baris terakhir dari setiap kelompok. Operasi ini mengasumsikan bahwa baris terakhir dalam pengelompokan adalah baris dengan cap waktu terbesar. Asumsi itu valid hanya bila pengelompokan menghormati urutan kronologi, sehingga sebelum groupby biasanya perlu dilakukan sort_values("ts"). Tanpa pengurutan, baris “terakhir” hanyalah baris yang kebetulan berada di ujung, bukan baris dengan cap waktu terbesar.

Agregasi atas jendela waktu#

Pola ketiga adalah agregasi atas jendela waktu, yaitu pemangkasan data mentah menjadi ringkasan per interval. Contoh klasiknya adalah “rerata suhu per 15 menit” atau “jumlah titik per jam”. Pola ini menjadi inti dari hampir setiap dashboard dan alerting, sebab data mentah per detik terlalu padat untuk ditampilkan langsung. Operasi ini disebut bucketing atau windowing, dan pandas menyediakannya melalui resample, sementara SQL menyediakannya melalui fungsi seperti time_bucket pada TimescaleDB atau date_bin pada Postgres 14+.

Agregasi jendela menghasilkan kolom baru yang khas, yaitu min, max, mean, dan count. Pemilihan agregat bergantung pada pertanyaan yang ingin dijawab: mean untuk kecenderungan umum, min/max untuk rentang ekstrem, dan count untuk verifikasi kelengkapan. Jendela yang lebih lebar, misalnya satu jam, menghasilkan rangkaian yang lebih halus tetapi menyembunyikan fluktuasi singkat. Jendela yang lebih sempit, misalnya satu menit, mempertahankan resolusi tetapi tidak mengurangi beban visual. Pilihan jendela adalah keputusan desain, bukan optimasi.

Tiga pola dalam satu dataset#

Skrip Python berikut membangkitkan 1.440 titik telemetri (satu hari penuh, interval satu menit) dengan satu sensor, lalu memperlihatkan ketiga pola dalam satu eksekusi. Pemindaian rentang memotong satu jam pada siang hari, last-known-value mengambil baris termutakhir, dan agregasi jendela menghitung min, max, mean, dan count per 15 menit. Skrip menggunakan pandas untuk keseluruhan operasi, dan pola yang sama dapat dipindahkan ke SQL tanpa perubahan semantik.

python agregasi_pembacaan.py
"""Demonstrasi pola pembacaan data time-series.

Skrip membangkitkan 1440 titik telemetri (satu hari, interval satu menit),
lalu memperlihatkan tiga pola pembacaan yang lazim pada data time-series,
yaitu pemindaian rentang (range scan), last-known-value per deret, dan
agregasi atas jendela waktu. Tujuannya menunjukkan bahwa pola pembacaan
time-series berbeda dari query operasional biasa.
"""
from __future__ import annotations

from datetime import datetime, timezone

import numpy as np
import pandas as pd

AWAL = datetime(2026, 1, 1, 0, 0, tzinfo=timezone.utc)
JUMLAH_TITIK = 1_440
FREKUENSI = "1min"


def bangun_telemetri() -> pd.DataFrame:
    cap_waktu = pd.date_range(AWAL, periods=JUMLAH_TITIK, freq=FREKUENSI, tz="UTC")
    rng = np.random.default_rng(42)
    suhu = 24.0 + np.sin(np.arange(JUMLAH_TITIK) / 60.0) + rng.normal(0, 0.2, JUMLAH_TITIK)
    return pd.DataFrame({
        "ts": cap_waktu,
        "sensor_id": "sensor-01",
        "suhu_c": np.round(suhu, 2),
    }).set_index("ts")


def pemindaian_rentang(df: pd.DataFrame) -> pd.DataFrame:
    awal = AWAL.replace(hour=12, minute=0, second=0)
    akhir = awal + pd.Timedelta(hours=1)
    return df.loc[awal:akhir]


def last_known_value(df: pd.DataFrame) -> pd.DataFrame:
    return df.groupby("sensor_id").tail(1)


def agregasi_jendela(df: pd.DataFrame) -> pd.DataFrame:
    return (
        df.resample("15min")["suhu_c"]
        .agg(["min", "max", "mean", "count"])
        .round(2)
    )


def main() -> None:
    df = bangun_telemetri()
    print("jumlah baris:", len(df))
    print("rentang waktu:", df.index.min(), "->", df.index.max())
    print()

    rentang = pemindaian_rentang(df)
    print("pemindaian rentang 12:00-13:00:", len(rentang), "baris")
    print(rentang.head(3).to_string())
    print("...")
    print(rentang.tail(2).to_string())
    print()

    lk = last_known_value(df)
    print("last-known-value per sensor:")
    print(lk.to_string())
    print()

    agg = agregasi_jendela(df)
    print("agregasi 15 menit (5 baris pertama):")
    print(agg.head(5).to_string())
    print()
    print("agregasi 15 menit (5 baris terakhir):")
    print(agg.tail(5).to_string())


if __name__ == "__main__":
    main()
text Keluaran
jumlah baris: 1440
rentang waktu: 2026-01-01 00:00:00+00:00 -> 2026-01-01 23:59:00+00:00

pemindaian rentang 12:00-13:00: 61 baris
                           sensor_id  suhu_c
ts                                          
2026-01-01 12:00:00+00:00  sensor-01   23.44
2026-01-01 12:01:00+00:00  sensor-01   23.70
2026-01-01 12:02:00+00:00  sensor-01   23.04
...
                           sensor_id  suhu_c
ts                                          
2026-01-01 12:59:00+00:00  sensor-01   24.14
2026-01-01 13:00:00+00:00  sensor-01   24.41

last-known-value per sensor:
                           sensor_id  suhu_c
ts                                          
2026-01-01 23:59:00+00:00  sensor-01   23.31

agregasi 15 menit (5 baris pertama):
                             min    max   mean  count
ts                                                   
2026-01-01 00:00:00+00:00  23.68  24.44  24.12     15
2026-01-01 00:15:00+00:00  24.08  24.60  24.36     15
2026-01-01 00:30:00+00:00  24.36  24.91  24.60     15
2026-01-01 00:45:00+00:00  24.47  25.11  24.78     15
2026-01-01 01:00:00+00:00  24.50  25.08  24.86     15

agregasi 15 menit (5 baris terakhir):
                             min    max   mean  count
ts                                                   
2026-01-01 22:45:00+00:00  22.90  23.55  23.28     15
2026-01-01 23:00:00+00:00  22.57  23.40  23.03     15
2026-01-01 23:15:00+00:00  22.75  23.42  23.06     15
2026-01-01 23:30:00+00:00  22.69  23.45  23.11     15
2026-01-01 23:45:00+00:00  22.48  23.31  22.98     15

Keluaran di atas memperlihatkan tiga hal penting. Pertama, pemindaian rentang satu jam menghasilkan 61 baris, yaitu setiap menit dari 12:00 hingga 13:00 inklusif. Kedua, last-known-value mengembalikan satu baris dengan cap waktu 23:59, yang merupakan baris terakhir deret. Ketiga, agregasi 15 menit menghasilkan rangkaian yang jauh lebih padat, yaitu 96 baris dari yang sebelumnya 1.440, dengan setiap baris merangkum min, max, mean, dan count. Angka count yang konstan bernilai 15 menunjukkan tidak ada celah pada deret, sedangkan nilai yang berbeda dari 15 akan menjadi penanda data hilang.

Kesalahan umum#

  • Mengambil baris terakhir tanpa mengurutkan menurut cap waktu. Pada data dengan late arrival, baris “terakhir” secara fisik belum tentu memiliki cap waktu terbesar, sehingga last-known-value menjadi salah.
  • Memilih resolusi jendela yang terlalu halus untuk dashboard. Resolusi satu menit pada layar 1.080 piksel menghasilkan kepadatan titik yang menenggelamkan pola, sehingga agregasi 15 menit atau satu jam biasanya lebih tepat untuk visualisasi.
  • Mengabaikan kolom count pada agregasi. Tanpa count, tidak ada cara untuk membedakan rerata yang dihitung dari 15 titik dengan rerata yang dihitung dari 3 titik, padahal keduanya memiliki keandalan yang sangat berbeda.

Ringkasan#

  • Tiga pola pembacaan khas time-series adalah pemindaian rentang, last-known-value, dan agregasi atas jendela waktu.
  • Pemindaian rentang memanfaatkan keterurutan untuk memangkas pencarian ke sebagian kecil tabel.
  • last-known-value bergantung pada indeks (sensor_id, ts DESC) dan pada urutan kronologi yang benar sebelum groupby.
  • Agregasi jendela mengubah data mentah per detik menjadi rangkaian per menit atau per jam yang layak ditampilkan, dengan kolom count sebagai penanda kelengkapan.

Referensi#