Deret 730 angka di Command Window tidak bisa dibaca manusia — mata kita tidak dirancang menangkap pola dari kolom angka. Visualisasi mengubah deret bersih hasil Bagian 2 menjadi tiga pertanyaan yang dijawab gambar: bagaimana kelakuan keseluruhan deret, kapan musim panas dan dingin terjadi, dan apakah tahun kedua berbeda dari tahun pertama. Semua figur dibuat headless — dibangun tanpa jendela, disimpan sebagai PNG — sehingga fungsi ini tetap bekerja di server tanpa layar dan di dalam test otomatis.
Yang kita bangun pada bagian ini:
visualisasiDeret— satu fungsi, tiga figur, semuanya PNG,- moving average 30 hari — penyaring derau yang memisahkan sinyal dari keramaian,
- profil musiman — agregasi 730 hari menjadi kurva 365 hari-dalam-tahun,
- perbandingan tahunan — dua subplot dengan sumbu identik.
Tiga Figur, Tiga Pertanyaan #
| Figure | Isi | Pertanyaan yang dijawab | Berkas |
|---|---|---|---|
| 1 | deret harian (garis tipis) + MA 30 hari (garis tebal) | Bagaimana bentuk keseluruhan 2 tahun? | deret_harian.png |
| 2 | rata-rata suhu per hari-dalam-tahun | Kapan puncak dan lembah musim? | profil_musiman.png |
| 3 | tahun 1 dan tahun 2, subplot bertumpuk, sumbu sama | Apakah kedua tahun serupa? | perbandingan_tahunan.png |
Pemisahan ini bukan gaya-gayaan. Figur gabungan 730 titik menjawab pertanyaan bentuk, tetapi terlalu ramai untuk membandingkan dua tahun; profil musiman menjawab pertanyaan waktu, tetapi membutuhkan agregasi yang menghapus tahun. Satu gambar untuk satu pertanyaan — karya pembersih figur yang mahir justru menolak menjejalkan semua ke satu axes.
Moving Average: Menyaring Derau, Menampakkan Sinyal #
Suhu harian bergoyang ±0.5 derajat karena derau dan siklus mingguan. Goyangan itu — gladi bising — menyembunyikan bentuk besar: musiman dan tren. Moving average (MA) mengganti setiap titik dengan rata-rata jendela 30 hari sebelumnya, sehangkan frekuensi tinggi saling meniadakan dan frekuensi rendah lolos. Versi mini dengan jendela 3 hari memperlihatkan mekanismenya:
s = [20; 22; 24; 26; 28];
n = numel(s);
ma3 = nan(n, 1);
for t = 3:n
ma3(t) = mean(s(t-2:t));
end
fprintf(' suhu MA3\n');
for t = 1:n
if isnan(ma3(t))
fprintf('%5.0f %5s\n', s(t), 'NaN');
else
fprintf('%5.0f %5.1f\n', s(t), ma3(t));
end
endOutput yang Dihasilkan:
suhu MA3
20 NaN
22 NaN
24 22.0
26 24.0
28 26.0Dua nilai pertama NaN: jendela 3 hari baru terisi mulai titik ketiga — MA trailing tidak pernah menjangkau ke belakang titik itu. Konsekuensinya, MA 30 hari kita baru valid dari hari ke-30, dan test Bagian 4 memastikan tepat 29 NaN di awal. Jendela 30 hari dipilih karena dua kali lipat siklus mingguan 7 hari (nyaris) dan jauh lebih pendek dari musiman 365 hari — cukup panjang untuk meredam derau, cukup pendek untuk tidak meratakan musim.
Figure 1: Deret Penuh dan MA 30 Hari #
Potongan pertama src/visualisasiDeret.m — persiapan data dan figur pertama:
if nargin < 3 || isempty(folderSimpan)
folderSimpan = pwd;
end
hari = hari(:);
suhu = suhu(:);
n = numel(hari);
% ---- moving average 30 hari (trailing) ----
ma30 = hitungMA30(suhu);
% ---- profil musiman: rata-rata per hari-dalam-tahun ----
doy = mod(hari - 1, 365) + 1; % hari ke-366 kembali ke 1, dst.
profil = zeros(365, 1);
for d = 1:365
profil(d) = mean(suhu(doy == d));
end
% ---- figure 1: deret penuh + MA 30 hari ----
fig1 = figure('Visible', 'off');
plot(hari, suhu, 'Color', [0.65 0.65 0.65], 'LineWidth', 0.5);
hold on;
plot(hari, ma30, 'b-', 'LineWidth', 2);
hold off;
xlabel('Hari ke-');
ylabel('Suhu (\circC)');
title('Suhu harian 2 tahun dan moving average 30 hari');
legend('Suhu harian', 'MA 30 hari', 'Location', 'northwest');
saveas(fig1, fullfile(folderSimpan, 'deret_harian.png'));
close(fig1);Mari kita bedah baris-baris kode di atas:
- Baris 24 (
doy = mod(hari - 1, 365) + 1;): pemetaan hari-dalam-tahun.mod(hari-1, 365)menghasilkan 0..364 berulang; ditambah 1 menjadi 1..365. Hari ke-1, 366, dan 731 semuanya memetakan kedoy1 — dua tahun kita dilipat menjadi satu tahun untuk agregasi musiman. - Baris 26-28 (
for d = 1:365 ... profil(d) = mean(suhu(doy == d));): agregasi via logical indexing.doy == dmemilih semua hari (tepatnya dua hari — satu per tahun) yang jatuh pada hari-dalam-tahun yang sama;meanmeratakannya. Hasilnya 365 nilai: rata-rata lintas tahun per posisi kalender. - Baris 31 (
fig1 = figure('Visible', 'off');): figur dibuat dalam keadaan tak terlihat. Di sesi interaktif MATLAB figur tetap tersimpan di memori;saveasbekerja tanpa jendela pernah terbuka — inilah mode yang aman untuk server dan test. - Baris 32-35 (
plot(hari, suhu, 'Color', [0.65 0.65 0.65], ...): deret harian digambar dulu, dengan warna abu-abu danLineWidth0.5 — tipis dan pucat, karena perannya latar.hold onmenjaga axes tetap terbuka; MA menyusul dengan biru pekatLineWidth2. Urutan dan ketebalan inilah hierarki visual: yang penting menonjol, yang pendukung menyurut. - Baris 39 (
legend('Suhu harian', 'MA 30 hari', 'Location', 'northwest');): legend dipatok di kiri-atas — area kosong di data menurun — supaya tidak menutup kurva. TanpaLocation, MATLAB meletakkannya “best fit” yang kadang pas di atas garis. - Baris 40-41 (
saveas(fig1, ...); close(fig1);): simpan lalu tutup.closemembebaskan memori figur — kebiasaan wajib untuk fungsi yang membuat banyak figur; tanpa itu, workspace test akan menumpuk lusinan figur tersembunyi.
Figure 2 dan 3: Profil Musiman dan Perbandingan Tahun #
Potongan kedua — dua figur sisanya:
% ---- figure 2: profil musiman ----
fig2 = figure('Visible', 'off');
plot((1:365)', profil, 'r-', 'LineWidth', 1.5);
xlabel('Hari dalam tahun (1-365)');
ylabel('Suhu (\circC)');
title('Profil musiman: rata-rata per hari-dalam-tahun');
saveas(fig2, fullfile(folderSimpan, 'profil_musiman.png'));
close(fig2);
% ---- figure 3: tahun 1 vs tahun 2 ----
fig3 = figure('Visible', 'off');
subplot(2, 1, 1);
plot(hari(1:365), suhu(1:365), 'Color', [0.65 0.65 0.65], 'LineWidth', 0.5);
hold on;
plot(hari(1:365), ma30(1:365), 'b-', 'LineWidth', 2);
hold off;
xlim([1 365]);
ylim([19 36]);
xlabel('Hari dalam tahun');
ylabel('Suhu (\circC)');
title('Tahun 1: deret harian + MA 30 hari');
legend('Suhu harian', 'MA 30 hari', 'Location', 'northwest');
subplot(2, 1, 2);
plot(hari(366:730) - 365, suhu(366:730), 'Color', [0.65 0.65 0.65], ...
'LineWidth', 0.5);
hold on;
plot(hari(366:730) - 365, ma30(366:730), 'b-', 'LineWidth', 2);
hold off;
xlim([1 365]);
ylim([19 36]);
xlabel('Hari dalam tahun');
ylabel('Suhu (\circC)');
title('Tahun 2: deret harian + MA 30 hari');
saveas(fig3, fullfile(folderSimpan, 'perbandingan_tahunan.png'));
close(fig3);Bedahnya:
- Baris 45 (
plot((1:365)', profil, 'r-', 'LineWidth', 1.5);): profil musiman cukup satu kurva merah sedang — tanpa derau yang perlu diredam, agregasi sudah bekerja. Kurvanya mulus naik-turun: lembah di sekitar hari 1-15 (Desember–Januari) dan puncak di sekitar hari 180-200 (Juli). - Baris 54 dan 66 (
subplot(2, 1, 1);/subplot(2, 1, 2);): satu figur dibagi dua axes bertumpuk. Tahun 1 di atas, tahun 2 di bawah — mata membandingkan vertikal lebih teliti daripada overlay warna yang saling menutup. - Baris 67 (
plot(hari(366:730) - 365, ...): kunci perbandingan — sumbu-x tahun 2 digeser dengan- 365sehingga kedua subplot menampilkan rentang x identik 1..365. Tanpa geser ini, tahun 2 tergambar di sumbu 366..730 dan pola musimnya tampak “tergeser setengah layar”. - Baris 59-60 dan 72-73 (
xlim([1 365]); ylim([19 36]);): kedua subplot dipatok ke sumbu yang sama persis. Batas y 19..36 dipilih melebihi data (20.6 sampai 34.0) agar kurva tidak menempel tepi. Dengan sumbu bebas, MATLAB memilih batas berbeda per subplot — dan selisih 1 derajat di kedua tahun akan terlihat seperti perbedaan dramatis padahal cuma skala.
Sisa berkas mengemas hasil: struct hasil berisi ma30, doy, profilMusiman, dan daftar nama berkas, lalu fprintf melaporkan ketiga PNG. Fungsi ditutup local function hitungMA30 — trailing 30 hari, 29 NaN di awal — yang juga dijalankan di analisisTren (disalin agar tiap berkas berdiri sendiri). Menjalankan fungsi pada deret bersih:
Output yang Dihasilkan:
=== Visualisasi Deret Waktu ===
Gambar 1 disimpan: hasil_cuaca/deret_harian.png
Gambar 2 disimpan: hasil_cuaca/profil_musiman.png
Gambar 3 disimpan: hasil_cuaca/perbandingan_tahunan.png
Amplitudo profil musiman: 12.96 derajat (max 33.81, min 20.85)Angka terakhir adalah angka penting: amplitudo profil musiman 12.96 derajat — puncak 33.81 di pertengahan tahun, lembah 20.85 di awal tahun. Rentang 11-13 derajat itulah sidik jari iklim tropis Indonesia, dan test Bagian 4 akan menjaganya tetap di rentang tersebut.
Kesalahan Umum Menggambar Deret Waktu #
| Kesalahan | Akibat | Perbaikan |
|---|---|---|
plot(hari, suhu, 'o-') — marker penuh | 730 lingkaran menyatu jadi garis tebal berbintik | garis saja, '-', tanpa marker |
| Tanpa MA | derau menguasai gambar, tren tak terlihat | tambahkan MA 30 hari sebagai garis tebal |
| Sumbu y otomatis per subplot | perbedaan kecil tampak raksasa | xlim/ylim identik antar subplot |
| Semua seri di satu axes | 1460 garis saling menutup | subplot terpisah atau agregasi profil |
Lupa close(fig) | figur menumpuk di memori saat loop | close segera setelah saveas |
Tiga figur kini tersimpan dan amplitudo musiman terukur. Yang tersisa dari proyek: mengubah pengamatan visual menjadi angka — slope tren per tahun, indeks bulanan, dan laporan otomatis. Bagian 4 menutup proyek dengan statistik dan test.