Deret waktu cuaca jarang datang bersih: sensor mati satu hari, operator salah ketik 60 alih-alih 26, stasiun mengirim sel kosong. Pada bagian ini kita menyiapkan fondasi proyek: data sintetis 730 hari yang deterministik, ditulis sebagai CSV, lalu dibaca kembali dengan validasi struktur yang tegas. Data sintetis dipilih karena kita bisa menanam masalah pada posisi yang diketahui persis — pembersihan pada Bagian 2 pun bisa dibuktikan benar atau salah lewat test.
Yang kita bangun pada bagian ini:
buatDataCuaca— 730 hari suhu dari rumus tetap (tanparand), plus 8 hari NaN dan 3 outlier yang diinjeksikan,muatDataCuaca— pembaca CSV dengan guardreadmatrix/dlmreaddan validasi struktur,- format berkas — kontrak kolom
hari,suhuyang dijaga oleh error bertipe, - peta pipeline — alur enam tahap proyek dari data mentah sampai laporan.
Anatomi Deret Cuaca Harian #
Data cuaca harian paling sederhana punya dua kolom: kapan dan berapa. Kita memakai indeks hari 1..730 alih-alih tanggal kalender agar proyek bekerja di tipe double dan identik di MATLAB maupun GNU Octave — datetime hanya dimiliki MATLAB. Kontrak berkas kita:
| Kolom | Isi | Satuan | Rentang wajar |
|---|---|---|---|
hari | indeks hari sejak awal pengamatan | hari (tanpa satuan) | 1 sampai 730 |
suhu | suhu udara rata-rata harian | derajat Celsius | 10 sampai 40 (iklim Indonesia) |
Baris pertama adalah header, sisanya 730 baris data. Nilai yang hilang disimpan sebagai teks NaN sehingga terbaca kembali sebagai NaN — bukan 0 yang diam-diam merusak statistik. Berikut cuplikan berkas hasil buatDataCuaca (baris ke-47 dan ke-88 memperlihatkan masalah yang ditanam):
hari,suhu
1,21.26195484
2,21.48997127
3,21.02610881
4,20.8593333
...
47,NaN
...
88,-5Selisih 12 derajat antara puncak dan lembah musiman adalah kisaran tahunan Indonesia yang sehat; -5 dan 60 mustahil secara fisik — kedua jenis angka ini menjadi alat uji kebenaran pembersihan nanti.
Merancang Data Sintetis yang Deterministik #
Rumus pembangkit dirangkai dari komponen fisik yang bisa dibedah, dan sama sekali tidak memakai angka acak:
| Komponen | Rumus | Peran |
|---|---|---|
| Baseline | 27 | rata-rata tahunan iklim Indonesia |
| Musiman | 6*cos(2*pi*(t-183)/365) | puncak awal Juli, lembah pergantian tahun; amplitudo 12 derajat |
| Tren | 0.001*t | pemanasan 0.365 derajat per tahun |
| Mingguan | 0.4*sin(2*pi*t/7) | variasi siklus 7 hari |
| Derau | 0.4*(mod(t*37,100)/100 - 0.5) | nilainya unik per hari, tetap deterministik |
Komponen terakhir adalah pengganti rand: mod(t*37, 100) memetakan setiap hari ke sisa pembagian yang berbeda — polanya seperti derau namun direproduksi bit-per-bit. Fase musiman sengaja dipusatkan di hari ke-183: puncak jatuh di awal Juli, dan simetri ini membuat komponen musiman tidak mencemari slope tren pada Bagian 4. Masalah lalu ditanam pada posisi tetap:
| Hari ke- | Masalah | Nilai tercatat |
|---|---|---|
| 47, 130, 262, 349 | NaN (sensor mati) | NaN |
| 478, 555, 640, 722 | NaN (sensor mati) | NaN |
| 88 | outlier dingin tak fisik | -5 |
| 365 | outlier panas tak fisik | 60 |
| 583 | outlier panas tak fisik | 45 |
buatDataCuaca: Menulis CSV dengan Guard #
Berikut src/buatDataCuaca.m secara utuh:
function data = buatDataCuaca(namaFile)
% BUATDATACUACA Membuat deret suhu harian sintetis lalu menyimpannya ke CSV.
% data = buatDataCuaca() -> menulis data_cuaca.csv di folder kerja
% data = buatDataCuaca('cuaca.csv') -> menulis ke lokasi tertentu
%
% Data 730 hari (2 tahun sintetis) dibentuk dari rumus deterministik,
% TANPA angka acak sama sekali:
% suhu(t) = 27 (rata-rata tahunan iklim Indonesia)
% + 6*cos(2*pi*(t-183)/365) (musiman: puncak awal Juli, lembah Desember)
% + 0.001*t (tren pemanasan 0.365 derajat/tahun)
% + 0.4*sin(2*pi*t/7) (variasi mingguan)
% + 0.4*(mod(t*37,100)/100 - 0.5) (derau unik per hari, tetap)
%
% Masalah data sengaja diinjeksikan pada posisi tetap:
% - 8 hari kosong (NaN) : hari ke-47, 130, 262, 349, 478, 555, 640, 722
% - 3 outlier tak fisik : hari ke-88 (-5), 365 (60), 583 (45)
if nargin < 1 || isempty(namaFile)
namaFile = 'data_cuaca.csv';
end
nHari = 730;
hari = (1:nHari)';
% ---- komponen rumus (semua deterministik) ----
musiman = 6*cos(2*pi*(hari - 183)/365); % periode 365 hari
tren = 0.001*hari; % 0.001 derajat/hari
mingguan = 0.4*sin(2*pi*hari/7);
derau = 0.4*(mod(hari*37, 100)/100 - 0.5); % -0.2 .. +0.2, unik per hari
suhuAsli = 27 + musiman + tren + mingguan + derau;
% ---- injeksi masalah pada posisi tetap ----
posisiNaN = [47; 130; 262; 349; 478; 555; 640; 722];
posisiOutlier = [88; 365; 583];
nilaiOutlier = [-5; 60; 45];
suhu = suhuAsli;
suhu(posisiNaN) = NaN;
suhu(posisiOutlier) = nilaiOutlier;
% ---- tulis CSV: header manual, data via writematrix (MATLAB) / dlmwrite (Octave) ----
fid = fopen(namaFile, 'w');
if fid == -1
error('buatDataCuaca:berkas', 'Tidak dapat menulis berkas: %s', namaFile);
end
fprintf(fid, 'hari,suhu\n');
fclose(fid);
if ~isempty(which('writematrix'))
writematrix([hari suhu], namaFile, 'WriteMode', 'append');
else
dlmwrite(namaFile, [hari suhu], 'precision', '%.10g', '-append');
end
data = struct();
data.namaFile = namaFile;
data.hari = hari;
data.suhu = suhu; % deret dengan masalah (yang ditulis ke CSV)
data.suhuAsli = suhuAsli; % deret sempurna sebelum injeksi (untuk uji)
data.posisiNaN = posisiNaN;
data.posisiOutlier = posisiOutlier;
data.nilaiOutlier = nilaiOutlier;
fprintf('Data cuaca sintetis: %d hari ditulis ke %s\n', nHari, namaFile);
fprintf('Masalah terinjeksi : %d hari NaN, %d outlier tak fisik\n', ...
numel(posisiNaN), numel(posisiOutlier));
endMari kita bedah baris-baris kode di atas:
- Baris 22-23 (
nHari = 730; hari = (1:nHari)';): dua tahun sintetis = 730 hari. Operator'menjadikanharivektor kolom — orientasi yang dipertahankan di seluruh proyek. - Baris 26-29 (
musiman = ...sampaiderau = ...): keempat komponen dihitung sekaligus lewat operasi vektor, tanpa loop — satu bariscosmenghasilkan 730 nilai. - Baris 33-35 (
posisiNaN = ...dst.): posisi masalah adalah konstanta, bukan hasil pencarian — fielddata.posisiNaNdandata.posisiOutlierdipakai test untuk memastikan deteksi Bagian 2 menemukan persis titik-titik ini. - Baris 37-39 (
suhu(posisiNaN) = NaN;): injeksi lewat index assignment — satu baris menimpa delapan posisi sekaligus; trik yang sama mengganti tiga nilai suhu dengan angka mustahil. - Baris 42-47 (
fid = fopen(...)sampaifclose(fid);): header ditulis manual denganfprintfke file handle — portabel di semua engine dan membuat baris pertama pasti seragam. - Baris 49-53 (
if ~isempty(which('writematrix')) ... end): guard penulis data.whichmengembalikan lokasi fungsi jika tersedia dan string kosong jika tidak — MATLAB jatuh kewritematrix, Octave kedlmwrite, keduanya menulis mode append agar header tidak tertimpa. - Baris 55-62 (
data = struct(); ...): keluaran berupa struct. FieldsuhuAsli(deret sebelum injeksi) adalah “kunci jawaban” untuk test — pada data nyata field ini tidak ada. - Baris 64-66 (
fprintf(...)): ringkasan dua baris memberi tahu pemanggil berkas mana yang ditulis dan berapa masalah yang tertanam.
Menjalankan fungsi tersebut:
Output yang Dihasilkan:
Data cuaca sintetis: 730 hari ditulis ke data_cuaca.csv
Masalah terinjeksi : 8 hari NaN, 3 outlier tak fisikmuatDataCuaca: Membaca Kembali dengan Validasi #
Berikut src/muatDataCuaca.m, pembaca dengan guard kebalikan:
function d = muatDataCuaca(namaFile)
% MUATDATACUACA Membaca CSV deret cuaca harian dan memvalidasi strukturnya.
% d = muatDataCuaca() -> membaca data_cuaca.csv di folder kerja
% d = muatDataCuaca('cuaca.csv') -> membaca lokasi tertentu
%
% Format berkas: baris pertama header "hari,suhu", lalu 730 baris data.
% Nilai suhu yang hilang tersimpan sebagai NaN.
% Struktur keluaran:
% d.hari, d.suhu, d.jumlahHari, d.jumlahMissing, d.jumlahOutlierDetected
if nargin < 1 || isempty(namaFile)
namaFile = 'data_cuaca.csv';
end
if exist(namaFile, 'file') ~= 2
error('muatDataCuaca:berkas', 'Berkas tidak ditemukan: %s', namaFile);
end
% ---- baca matriks: readmatrix (MATLAB R2019a+) / dlmread (Octave) ----
% Baris header dilewati: readmatrix mendeteksinya otomatis, sedangkan
% dlmread perlu indeks baris awal 1 (menghitung dari nol, artinya baris ke-2).
if ~isempty(which('readmatrix'))
M = readmatrix(namaFile);
else
M = dlmread(namaFile, ',', 1, 0);
end
% ---- validasi struktural ----
if size(M, 2) ~= 2
error('muatDataCuaca:kolom', ...
'CSV harus punya 2 kolom (hari,suhu), terdeteksi %d kolom.', size(M, 2));
end
if size(M, 1) < 2
error('muatDataCuaca:kosong', 'CSV tidak memuat baris data.');
end
d = struct();
d.namaFile = namaFile;
d.hari = M(:, 1);
d.suhu = M(:, 2);
d.jumlahHari = numel(d.hari);
% kolom hari wajib 1..N berurutan tanpa lompatan
if ~isequal(d.hari, (1:d.jumlahHari)')
error('muatDataCuaca:urutan', ...
'Kolom hari harus berisi 1 sampai %d berurutan.', d.jumlahHari);
end
% pemindaian cepat (deteksi rinci ada di deteksiMasalah)
d.jumlahMissing = sum(isnan(d.suhu));
d.jumlahOutlierDetected = sum(~isnan(d.suhu) & (d.suhu < 10 | d.suhu > 40));
fprintf('Berkas dimuat: %s\n', namaFile);
fprintf('Struktur valid : %d hari, kolom hari berurutan 1..%d\n', ...
d.jumlahHari, d.jumlahHari);
fprintf('Pemindaian awal: %d nilai NaN, %d outlier tak fisik\n', ...
d.jumlahMissing, d.jumlahOutlierDetected);
endMari kita bedah baris-baris kode di atas:
- Baris 15-17 (
if exist(namaFile, 'file') ~= 2): keberadaan berkas diperiksa sebelum membaca, dan pesan galatnya menyebut nama berkas — pengguna langsung tahu path mana yang salah. - Baris 22-26 (
if ~isempty(which('readmatrix')) ...): guard pembaca, kebalikan guard penulis.readmatrixmelewati header otomatis;dlmreadperlu titik awal — argumen1, 0(baris, kolom, berbasis nol) berarti “mulai baris kedua, kolom pertama”. - Baris 29-35 (
if size(M, 2) ~= 2 ...): validasi bentuk. CSV yang hanya punya satu kolom ditolak lebih dulu, sebelum menyebabkan galat indexing yang membingungkan di fungsi lain. - Baris 44-47 (
if ~isequal(d.hari, (1:d.jumlahHari)')): pemeriksaan rentang sekaligus urutan — perbandingan terhadap(1:N)'menolak hari terlewat, terduplikasi, maupun tidak berurut dalam satu ekspresi. - Baris 50-51 (
d.jumlahMissing = sum(isnan(d.suhu));dst.): pemindaian cepat saat muat — NaN lewatsum(isnan(...)), outlier fisik lewat logical indexing majemuk. - Baris 53-57 (
fprintf(...)): tiga baris ringkasan — sumber, struktur, dan isi. Nilaid.jumlahOutlierDetectedkita adalah 3, cocok dengan tabel injeksi di atas.
Membaca berkas hasil buatDataCuaca:
Output yang Dihasilkan:
Berkas dimuat: data_cuaca.csv
Struktur valid : 730 hari, kolom hari berurutan 1..730
Pemindaian awal: 8 nilai NaN, 3 outlier tak fisikValidasi terbukti ketika diberi tiga berkas rusak — hilang, kolom tidak pas, hari melompat dari 1 ke 3:
Output yang Dihasilkan:
galat 1: Berkas tidak ditemukan: data_tidak_ada.csv
galat 2: CSV harus punya 2 kolom (hari,suhu), terdeteksi 1 kolom.
galat 3: Kolom hari harus berisi 1 sampai 3 berurutan.Alur Pipeline dari Sini #
Diagram berikut memetakan sisa proyek — setiap kotak adalah satu berkas di src/ yang disusun oleh jalankanAnalisisCuaca:
Bagian 2 membedah cara menemukan titik-titik bermasalah itu — dua lapis pemeriksaan, domain fisik dan statistik — lalu memperbaikinya tanpa mengubah panjang deret.