Data deret waktu jarang terekam pada momen yang sama persis: transaksi kartu masuk pukul 08:05, sedangkan tabel kurs hanya terekam pukul 08:00 dan 10:00. pd.merge() biasa tidak dapat membantu karena kuncinya tidak pernah identik, sehingga hasil gabungan kehilangan baris. pd.merge_asof() menyelesaikan masalah ini dengan mencari pasangan waktu terdekat sesuai arah yang kita tentukan, lengkap dengan batas jarak melalui parameter tolerance. Pada Chapter ini kita menggabungkan transaksi dengan kurs pada tanggal beku 15 Januari 2026, membandingkan tiga pilihan direction, dan mengelompokkan pencarian per gudang dengan by.

Menggabungkan Berdasarkan Kunci Waktu Terdekat #

pd.merge_asof(kiri, kanan, on="waktu") mengambil setiap baris tabel kiri lalu mencari satu baris tabel kanan yang kuncinya terdekat. Berbeda dengan merge biasa yang menuntut kecocokan persis, pencarian asof mengikuti arah waktu: ke belakang (nilai terakhir yang sudah lewat), ke depan (nilai berikutnya yang akan datang), atau ke yang paling dekat. File berikut memakai datetime yang dibuat pd.to_datetime() dengan tanggal beku 2026 agar hasilnya dapat direproduksi.

python
import pandas as pd

transaksi = pd.DataFrame({
    "waktu": pd.to_datetime([
        "2026-01-15 08:05", "2026-01-15 09:30", "2026-01-15 11:00",
    ]),
    "nominal_usd": [100, 80, 140],
})

kurs = pd.DataFrame({
    "waktu": pd.to_datetime(["2026-01-15 08:00", "2026-01-15 10:00"]),
    "rate": [15850.0, 15900.0],
})

# direction="backward": ambil nilai kurs terakhir pada/sebelum waktu transaksi
hasil = pd.merge_asof(transaksi, kurs, on="waktu")
print(hasil)
print()

# Bandingkan tiga pilihan direction pada data yang sama
fwd = pd.merge_asof(transaksi, kurs, on="waktu", direction="forward")
nearest = pd.merge_asof(transaksi, kurs, on="waktu", direction="nearest")
print(fwd["rate"].to_list())
print(nearest["rate"].to_list())
print()

# tolerance: pasangan yang lebih jauh dari 30 menit tidak digabung
toleran = pd.merge_asof(transaksi, kurs, on="waktu", direction="nearest",
                        tolerance=pd.Timedelta("30min"))
print(toleran)
print()

# by= memisahkan pencarian per kelompok
sensor = pd.DataFrame({
    "waktu": pd.to_datetime([
        "2026-01-15 08:05", "2026-01-15 08:20", "2026-01-15 09:30",
    ]),
    "gudang": ["A", "B", "A"],
    "suhu": [24.5, 26.1, 25.0],
})
pemeliharaan = pd.DataFrame({
    "waktu": pd.to_datetime(["2026-01-15 08:00", "2026-01-15 09:00"]),
    "gudang": ["A", "A"],
    "teknisi": ["Budi", "Siti"],
})
print(pd.merge_asof(sensor, pemeliharaan, on="waktu", by="gudang"))
print()

# merge_asof juga bekerja di atas datetime index
tr = transaksi.set_index("waktu")
kr = kurs.set_index("waktu")
print(pd.merge_asof(tr, kr, left_index=True, right_index=True))

Output yang Dihasilkan:

text
                waktu  nominal_usd     rate
0 2026-01-15 08:05:00          100  15850.0
1 2026-01-15 09:30:00           80  15850.0
2 2026-01-15 11:00:00          140  15900.0

[15900.0, 15900.0, nan]
[15850.0, 15900.0, 15900.0]

                waktu  nominal_usd     rate
0 2026-01-15 08:05:00          100  15850.0
1 2026-01-15 09:30:00           80  15900.0
2 2026-01-15 11:00:00          140      NaN

                waktu gudang  suhu teknisi
0 2026-01-15 08:05:00      A  24.5    Budi
1 2026-01-15 08:20:00      B  26.1     NaN
2 2026-01-15 09:30:00      A  25.0    Siti

                     nominal_usd     rate
waktu                                    
2026-01-15 08:05:00          100  15850.0
2026-01-15 09:30:00           80  15850.0
2026-01-15 11:00:00          140  15900.0

Mari kita telaah bagian-bagian pentingnya:

  1. Baris 3-13: kolom waktu bertipe datetime64[us], resolusi bawaan pandas 3.0 saat pd.to_datetime() memproses string tanggal.
  2. Baris 15-17 (direction default): tanpa argumen direction, pandas memakai "backward"; transaksi 08:05 dan 09:30 sama-sama memakai kurs 08:00 (15850.0), lalu transaksi 11:00 naik ke kurs 10:00 (15900.0).
  3. Baris 20-24: direction="forward" mengambil kurs berikutnya sehingga 08:05 memakai nilai 10:00 (15900.0) dan 11:00 tidak punya pasangan (nan); direction="nearest" memilih jarak terpendek, dan transaksi 09:30 yang berjarak tepat 30 menit dari 10:00 jatuh ke 15900.0.
  4. Baris 27-30 (tolerance): pasangan hanya dibentuk bila jarak waktunya paling lama 30 menit; transaksi 11:00 berjarak 60 menit dari kurs terakhir sehingga rate menjadi NaN, sinyal untuk ditinjau ulang.
  5. Baris 33-46 (by=): sensor gudang B tidak mewarisi teknisi gudang A; pencarian asof dijalankan di dalam masing-masing kelompok gudang.
  6. Baris 49-52: bila kedua tabel sudah memakai datetime index, parameter on diganti left_index=True dan right_index=True.

direction: backward, forward, nearest #

Pilihan direction menentukan makna bisnis hasil gabungan. "backward" paling umum untuk konteks “harga yang berlaku saat itu”, seperti kurs yang dicatat sebelum transaksi terjadi. "forward" cocok untuk jadwal: pelatihan berikutnya setiap pegawai, atau pengiriman yang baru akan berangkat. "nearest" berguna ketika dua tabel saling mendekati, misalnya perbandingan dua alat ukur yang tidak pernah menyala bersamaan. Ketiganya menerima tolerance dalam bentuk pd.Timedelta untuk kunci datetime atau bilangan biasa untuk kunci numerik, sehingga pasangan yang terlalu jauh otomatis menjadi NaN.

Kelompok dengan by= dan Index Waktu #

Parameter by menjaga agar pencarian tidak melompat antar kelompok; wajib dipakai saat tabel berisi lebih dari satu lokasi, cabang, atau perangkat. Variannya, left_by dan right_by, mengelompokkan hanya satu sisi bila label kelompok berbeda nama di kedua tabel. Ketika data sudah tersusun sebagai deret waktu ber-index, pola left_index=True, right_index=True menghindari set_index() ganda dan menghasilkan Series atau DataFrame dengan index waktu yang utuh.

Halaman user guide merging memuat bagian khusus asof merge, dokumentasi pandas.merge_asof merinci seluruh parameter, dan user guide time series melengkapi teknik pengolahan kunci waktu. Setelah penggabungan horizontal tuntas, satu anggota keluarga merge tersisa: merge_ordered() untuk union terurut waktu beserta anti-join baru pandas 3.0, dibahas pada Chapter berikutnya sebelum kita beralih ke pivot().