Jendela dari Bagian 2 masih diam: tombol Muat Data belum berbuat apa pun. Bagian ini menghidupkannya dengan urutan yang sengaja dibalik dari tampilan — logika dulu, penyambungan belakangan. Kita membangun analisisInti() (statistik + korelasi + laporan), dua pemasok datanya (buatDataContoh() dan muatCSV()), lalu menempelkan semuanya ke callback di aplikasiAnalisis.m. Seluruh fungsi logika di bagian ini murni — dan terbukti berjalan di GNU Octave dengan output nyata. Cakupannya:
analisisInti: struct masukan → struct hasil + laporan teks,buatDataContohdanmuatCSV: berkas CSV → struct data, lengkap dengan guard,- callback
ButtonPushedFcndanValueChangedFcn: baca → panggil → tulis, - jalur galat:
try/catchdi callback dan dialoguialert.
analisisInti: Jantung yang Teruji #
Fungsi ini menerima struct {data, namaKolom} dan mengembalikan struct hasil berisi statistik tiap kolom, korelasi kolom pertama terhadap kolom lain, dan laporan teks siap tampil. Simpan sebagai src/analisisInti.m:
function hasil = analisisInti(input)
% ANALISISINTI Menghitung statistik ringkas dari struct data.
% hasil = analisisInti(input)
%
% input (struct):
% input.data : matriks numerik (baris = observasi, kolom = variabel)
% input.namaKolom : cell array nama kolom (opsional; bila kosong
% dipakai nama bawaan kolom1, kolom2, ...)
%
% hasil (struct):
% hasil.jumlahBaris, hasil.jumlahKolom : ukuran data
% hasil.mean, hasil.stdDev : rata-rata & deviasi standar per kolom
% hasil.minNilai, hasil.maksNilai : nilai terkecil & terbesar per kolom
% hasil.korelasi : korelasi kolom 1 vs kolom 2..n
% hasil.laporan : laporan teks siap ditampilkan
% --- Guard bentuk input ---
if ~isfield(input, 'data') || ~isnumeric(input.data)
error('analisisInti:input', 'Field "data" harus berupa matriks numerik.');
end
data = input.data;
if isempty(data) || ndims(data) ~= 2
error('analisisInti:input', 'Field "data" tidak boleh kosong.');
end
nBaris = size(data, 1);
nKolom = size(data, 2);
if nKolom < 2
error('analisisInti:kolom', ...
'Data memerlukan minimal 2 kolom, ditemukan %d.', nKolom);
end
% --- Nama kolom: pakai yang diberikan atau nama bawaan ---
if ~isfield(input, 'namaKolom') || isempty(input.namaKolom)
namaKolom = arrayfun(@(k) sprintf('kolom%d', k), 1:nKolom, ...
'UniformOutput', false);
elseif iscell(input.namaKolom) && numel(input.namaKolom) == nKolom
namaKolom = input.namaKolom;
else
error('analisisInti:namaKolom', ...
'Jumlah namaKolom tidak sama dengan jumlah kolom data (%d).', nKolom);
end
% --- Statistik per kolom ---
hasil.jumlahBaris = nBaris;
hasil.jumlahKolom = nKolom;
hasil.namaKolom = namaKolom;
hasil.mean = mean(data, 1);
hasil.stdDev = std(data, 0, 1);
hasil.minNilai = min(data, [], 1);
hasil.maksNilai = max(data, [], 1);
% --- Korelasi kolom pertama vs tiap kolom lain ---
x = data(:, 1);
tengahX = x - mean(x);
bawahX = sum(tengahX .^ 2);
korelasi = zeros(1, nKolom - 1);
for k = 2:nKolom
y = data(:, k);
tengahY = y - mean(y);
bawahY = sum(tengahY .^ 2);
if bawahX == 0 || bawahY == 0
korelasi(k - 1) = NaN; % kolom konstan: korelasi tak terdefinisi
else
korelasi(k - 1) = sum(tengahX .* tengahY) / sqrt(bawahX * bawahY);
end
end
hasil.korelasi = korelasi;
% --- Laporan teks (tabel sprintf) ---
lp = sprintf('Laporan Analisis Data (%d baris)\n', nBaris);
lp = [lp, sprintf('%-12s %10s %10s %10s %10s\n', ...
'Kolom', 'Mean', 'Std', 'Min', 'Maks')];
for k = 1:nKolom
lp = [lp, sprintf('%-12s %10.3f %10.3f %10.3f %10.3f\n', ...
namaKolom{k}, hasil.mean(k), hasil.stdDev(k), ...
hasil.minNilai(k), hasil.maksNilai(k))];
end
lp = [lp, sprintf('\nKorelasi kolom "%s" vs kolom lain:\n', namaKolom{1})];
for k = 2:nKolom
lp = [lp, sprintf(' %s-%s : %7.3f\n', ...
namaKolom{1}, namaKolom{k}, korelasi(k - 1))];
end
hasil.laporan = lp;
endMari kita bedah baris-baris kode di atas:
- Baris 20-33 (guard bentuk): tiga pemeriksaan berurutan —
dataharus numerik, tidak kosong dan berdimensi dua, dan minimal 2 kolom. Data satu kolom ditolak karena korelasi kolom pertama vs kolom lain tidak punya lawan bicara. - Baris 35-46 (nama kolom): nama diterima bila cell array dengan panjang pas; kosong atau tidak ada → nama bawaan
kolom1,kolom2; panjang salah → galat eksplisit. Kontrak yang longgar diam-diam lebih berbahaya daripada galat yang jelas. - Baris 51-57 (statistik per kolom):
mean(data, 1)mengoperasi per kolom;std(data, 0, 1)— bendera0berarti deviasi standar contoh (pembagi N-1), bawaan statistik MATLAB. - Baris 59-72 (korelasi): korelasi Pearson kolom pertama vs kolom ke-k dihitung manual dari rumus. Kolom konstan membuat penyebut nol → hasil
NaNdijaga eksplisit, bukan pembagian nol diam-diam. - Baris 74-92 (laporan): tabel dirakit dengan
sprintf:%-12srata kiri untuk nama,%10.3frata kanan untuk angka — selaras dengan konvensi kolom MATLAB. Laporan ditempel per baris denganlp = [lp, ...].
Fungsi ini bisa dicoba saat itu juga tanpa GUI — driver kecil berikut dijalankan dari folder src/ (di Command Window MATLAB maupun Octave):
% driver sementara: uji lapisan logika tanpa GUI (jalankan dari folder src/)
buatDataContoh();
data = muatCSV('data_contoh.csv');
hasil = analisisInti(data);
fprintf('%s', hasil.laporan);Output yang Dihasilkan:
Laporan Analisis Data (12 baris)
Kolom Mean Std Min Maks
bulan 6.500 3.606 1.000 12.000
penjualan 291.667 58.517 200.000 380.000
suhu 26.917 1.019 25.500 28.500
Korelasi kolom "bulan" vs kolom lain:
bulan-penjualan : 0.997
bulan-suhu : 0.012Dua angka korelasi itu menyimpan cerita data: bulan-penjualan bernilai 0.997 karena penjualan naik hampir lurus sepanjang tahun, sedangkan bulan-suhu bernilai 0.012 karena suhu memuncak di tengah tahun lalu turun — pola melengkung yang tidak tertangkap korelasi linear. Ringkasan inilah yang akan dibaca pengguna aplikasi kita.
Memasok Data: buatDataContoh dan muatCSV #
buatDataContoh() menulis CSV dengan data tetap 12 baris x 3 kolom. Trik penulisannya satu baris sprintf: format '%d,%d,%.1f\n' diberikan matriks M', dan sprintf membaca argumen matriks kolom demi kolom — setiap kolom M' adalah satu baris data M:
% Satu baris CSV per baris data: bulan (bulat), penjualan (bulat),
% suhu (satu desimal). sprintf membaca M' kolom demi kolom, dan setiap
% kolom M' adalah satu baris data M.
teks = sprintf('%s,%s,%s\n', namaKolom{:});
teks = [teks, sprintf('%d,%d,%.1f\n', M')];
fid = fopen(namaBerkas, 'w');
if fid == -1
error('buatDataContoh:gagal', 'Gagal menulis berkas: %s', namaBerkas);
end
fprintf(fid, '%s', teks);
fclose(fid);Berkas data_contoh.csv yang dihasilkannya:
bulan,penjualan,suhu
1,200,26.0
2,215,26.5
3,230,25.5
4,255,27.0
5,270,28.0
6,290,28.5
7,305,28.5
8,320,27.5
9,330,27.0
10,345,26.5
11,360,26.0
12,380,26.0muatCSV() membaca berkas itu kembali baris demi baris dengan fgetl, memecah tiap baris dengan strsplit, dan mengubah tiap token menjadi angka — sambil menjaga seluruh kemungkinan rusaknya berkas:
% --- Baris data: angka-angka ---
barisData = [];
nBaris = 0;
while true
baris = fgetl(fid);
if ~ischar(baris) % fgetl mengembalikan -1 di akhir berkas
break;
end
if isempty(strtrim(baris)) % lewati baris kosong
continue;
end
nBaris = nBaris + 1;
token = strsplit(strtrim(baris), ',');
if numel(token) ~= nKolom
fclose(fid);
error('muatCSV:kolom', ...
'Baris %d memiliki %d kolom, header memiliki %d.', ...
nBaris, numel(token), nKolom);
end
nilai = zeros(1, nKolom);
for k = 1:nKolom
v = str2double(strtrim(token{k}));
if isnan(v)
fclose(fid);
error('muatCSV:angka', ...
'Baris %d kolom %d ("%s") bukan angka.', ...
nBaris, k, strtrim(token{k}));
end
nilai(k) = v;
end
barisData = [barisData; nilai]; %#ok<AGROW>
end
fclose(fid);Mari kita bedah baris-baris kode di atas:
- Baris 4-6 (
fgetl+ischar):fgetlmengembalikan teks per baris dan-1di akhir berkas — pemeriksaan~ischaradalah penanda berakhirnya loop. - Baris 7-9 (
strtrim): baris yang hanya berisi spasi dilewati, bukan dihitung sebagai baris rusak. - Baris 11-15 (
numel(token) ~= nKolom): jumlah token harus sama dengan jumlah kolom header — baris kehilangan koma langsung ditolak dengan pesan yang menyebut lokasinya. - Baris 16-23 (
str2double+isnan):str2doublemengembalikanNaN— bukan galat — untuk teks yang bukan angka, makaisnanadalah pemeriksaannya; pesan galat menyebut baris, kolom, dan isinya. - Baris 24 (
[barisData; nilai]): matriks tumbuh satu baris per baris berkas — cukup untuk data bulanan kecil.
Guard lain melengkapi pintu masuk dan keluar: berkas tidak ada (exist), header kosong, header hanya satu kolom, dan tidak ada baris data. Setiap penolakan memakai error dengan pesan berbahasa Indonesia — kontrak muatCSV mengembalikan struct {data, namaKolom} yang persis diterima analisisInti.
Callback: Menyambungkan UI dengan Logika #
Dengan logika siap, callback tombol di aplikasiAnalisis.m tinggal merangkai — MATLAB-only, berjalan hanya di desktop MATLAB:
function muatDataButtonPushed(~, ~)
btnMuat.Enable = 'off'; % cegah klik ganda selama proses
try
berkas = 'data_contoh.csv';
if ~exist(berkas, 'file')
buatDataContoh(berkas); % klik pertama membuat data contoh
end
dataBaru = muatCSV(berkas);
dataMatriks = dataBaru.data;
namaKolomCell = dataBaru.namaKolom;
hasilAnalisis = analisisInti(dataBaru);
ddKolom.Items = namaKolomCell(2:end); % kolom Y = selain kolom X
ddKolom.Value = namaKolomCell{2};
ddKolom.Enable = 'on';
perbaruiTampilan(namaKolomCell{2});
catch galat
uialert(fig, galat.message, 'Gagal Memuat Data', 'Icon', 'error');
end
btnMuat.Enable = 'on'; % pulihkan tombol apa pun hasilnya
endMari kita bedah baris-baris kode di atas:
- Baris 2 (
btnMuat.Enable = 'off'): tombol dikunci sebelum bekerja — klik ganda tidak bisa menumpuk dua kali pemuatan; event loop MATLAB memproses callback satu per satu. - Baris 4-6 (
exist+buatDataContoh): berkas contoh dibuat hanya bila belum ada, sehingga pengguna bisa mengedit CSV lalu menekan Muat Data ulang untuk memuat versi terbaru. - Baris 7-10 (muat → hitung): dua baris pemanggilan fungsi murni —
muatCSVlaluanalisisInti— lalu hasilnya disimpan ke state fungsi utama. Callback tidak berisi satu rumus pun. - Baris 12-14 (
Items,Value,Enable): dropdown diisi nama kolom selain kolom X (bulan), nilai awal kolom kedua, lalu diaktifkan. MengisiValuesecara programatik tidak memicuValueChangedFcn— pembaruan tampilan kita panggil sendiri di baris 15. - Baris 16-18 (
catch+uialert): galat dari guardmuatCSV/analisisIntiditangkap lalu ditampilkan sebagai dialog modal berikon error di depan jendela — pengguna membaca pesan yang jelas, bukan teks galat mentah. - Baris 20 (
Enable = 'on'): tombol dipulihkan setelahtry/catch— berapa pun hasilnya, aplikasi tidak pernah terkunci permanen.
Memperbarui Tampilan #
Pembantu perbaruiTampilan() dipakai ulang oleh dua callback — tombol dan dropdown — menggambar grafik dan menulis ringkasan untuk kolom Y terpilih:
function kolomYChanged(~, ~)
if isempty(dataMatriks)
return; % belum ada data: tidak ada yang perlu digambar
end
perbaruiTampilan(ddKolom.Value);
end
function perbaruiTampilan(namaKolomY)
idxY = find(strcmp(namaKolomCell, namaKolomY));
cla(axGrafik); % bersihkan grafik lama
plot(axGrafik, dataMatriks(:, 1), dataMatriks(:, idxY), ...
'LineWidth', 1.5, 'Marker', 'o');
axGrafik.XLabel.String = namaKolomCell{1};
axGrafik.YLabel.String = namaKolomY;
axGrafik.Title.String = sprintf('%s vs %s', ...
namaKolomY, namaKolomCell{1});
% Ringkasan tiga baris (dokumentasi uilabel: sprintf dengan \n
% menghasilkan teks multi-baris).
lblRingkasan.Text = sprintf( ...
'%d baris | kolom Y: %s\nmean %.1f | std %.1f | min %.1f | maks %.1f\nkorelasi %s-%s: %.3f', ...
hasilAnalisis.jumlahBaris, namaKolomY, ...
hasilAnalisis.mean(idxY), hasilAnalisis.stdDev(idxY), ...
hasilAnalisis.minNilai(idxY), hasilAnalisis.maksNilai(idxY), ...
namaKolomCell{1}, namaKolomY, ...
hasilAnalisis.korelasi(idxY - 1));
endTiga keputusan pentingnya: plot(axGrafik, ...) selalu menyebut target axes — tanpa target, MATLAB membuka jendela figure baru di luar aplikasi; cla(axGrafik) membersihkan grafik lama sebelum menggambar; dan label ringkasan diisi sprintf tiga baris yang menyisipkan \n. Nilai-nilai yang dialirkan ke lblRingkasan.Text bisa diperiksa tanpa GUI:
% teks yang akan diterima lblRingkasan (nilai sama dengan app sungguhan)
lblTeks = sprintf('%d baris | kolom Y: %s\nmean %.1f | std %.1f | min %.1f | maks %.1f\nkorelasi %s-%s: %.3f', ...
hasil.jumlahBaris, data.namaKolom{2}, hasil.mean(2), hasil.stdDev(2), ...
hasil.minNilai(2), hasil.maksNilai(2), ...
data.namaKolom{1}, data.namaKolom{2}, hasil.korelasi(1));
disp(lblTeks)Output yang Dihasilkan:
12 baris | kolom Y: penjualan
mean 291.7 | std 58.5 | min 200.0 | maks 380.0
korelasi bulan-penjualan: 0.997Saat pengguna memilih suhu, teks yang sama terbentuk ulang dari kolom ketiga: mean 26.9 | std 1.0 | min 25.5 | maks 28.5 dan korelasi bulan-suhu: 0.012 — grafik ikut berganti tanpa membaca berkas lagi.
Jalur Galat di Praktik #
Jalur catch callback terpicu oleh guard-guard muatCSV. Simulasi berikut membuktikan pesannya tanpa GUI:
% CSV rusak: token "abc" di kolom penjualan
fid = fopen('penjualan_rusak.csv', 'w');
fprintf(fid, 'bulan,penjualan,suhu\n1,abc,26.0\n');
fclose(fid);
try
muatCSV('penjualan_rusak.csv');
catch galat
fprintf('Galat ditangkap: %s\n', galat.message);
endOutput yang Dihasilkan:
Galat ditangkap: Baris 1 kolom 2 ("abc") bukan angka.Di aplikasi sungguhan, baris fprintf itu digantikan uialert(fig, galat.message, 'Gagal Memuat Data', 'Icon', 'error'): dialog modal muncul di depan jendela, pengguna membaca lokasi kesalahan, tombol dipulihkan, dan aplikasi tetap hidup untuk dicoba lagi. Aplikasi kini utuh — tugas terakhir kita adalah membuktikan semuanya bekerja: Bagian 4 menguji lapisan logika secara otomatis dan antarmuka secara terencana.