Google Cloud Platform Compute Engine

Google Cloud Platform memberikan 300$ credit untuk tahun pertama sebagai uji coba gratis. Artikel ini ditulis untuk membahas bagaimana cara deploy sebuah Compute Engine atau Virtual Machine yang bisa kita pasang di Google Cloud Platform.

Penulis bukan seorang Software Engineer dan tidak memiliki background di bidang IT, sehingga terminologi maupun penjelasan ini mungkin terdapat kekurangan, mohon sampaikan dalam komentar jika ada yang kurang tepat.

Google Cloud Platform adalah Infrastructure as a Service (IaaS)

Infrastruktur cloud juga dapat disebut Infrastructure as a Service (IaaS) adalah swalayan yang dapat memberikan akses komputasi tanpa membeli hardware. Infrasktruktur komputasi Cloud ini umumnya berdasarkan teknologi virtualisasi, pelanggan IaaS dapat mengakses melalui dashboard segala fungsi dari infrakstruktur yang diperlukan. Google Cloud Platform dan Amazon Web Services merupakan salah satu layanan IaaS yang tersedia dan memberikan uji coba gratis untuk tahun pertama.

Jika sebelumnya tren belajar Computer Science dan Linux dapat dilakukan dengan membeli perangkat keras Raspberry Pi atau perangkat Linux lainnya yang murah, sekarang tanpa membeli hardware kita dapat dengan mudah menggunakan IaaS untuk belajar komputasi.

Google Cloud Platform: Compute Engine

Menggunakan compute engine kita dapat membuat sebuah instance Virtual Machine dengan resource yang kita inginkan. Instance paling murah yang dapat kita deploy adalah 1vCPU dengan RAM 0.6GB (f1-micro). Untuk percobaan kali ini saya akan menggunakan small (1 shared vCPU dengan 1.7GB RAM). Saya akan memasang Ubuntu 18.04 dan menggunakan 10GB SSD.

GCP
Gambar 1. Setup Virtual Machine Instance di Google Compute Engine

SSH Key

Untuk mengakses instance dengan SSH, generate key SSH menggunakan PuTTY Key Generator untuk mendapatkan RSA key. RSA key ini kita masukkan pada bagian Security di menu setup Virtual Machine dan copy hasil RSA key ini di SSH key text box. Lalu simpan RSA key sebagai private key file (*.ppk) untuk dapat digunakan melalui PuTTY.

puttygen.PNG
Gambar 2. Key SSH untuk mengakses Virtual Machine instance

Akses SSH

Setelah memasukkan key pada PuTTY, buka koneksi SSH melalui IP address dari instance Google Compute Engine yang sudah dibuat. Masukkan username sesuai dengan comment dari SSH key. Jika sudah masuk melalui username, Anda sudah dapat mengakses Virtual Machine Ubuntu 18.04.2 LTS yang sudah terpasang di Google Cloud Platform.

putty.PNG

Selamat mencoba Google Cloud Platform untuk kepentingan komputasi Anda.

Prinsip Spread Spectrum dan Contoh Aplikasinya: LoRa

Pendahuluan

Spread Spectrum adalah prinsip yang mendasari banyak teknologi yang menjadi penting di dunia elektronika. Mungkin istilah Spread Spectrum terdengar asing, namun jika kita membahas Bluetooth (802.15.1) dan WiFi (IEEE 802.11) mungkin Anda lebih familiar. Saya ingin membawa Anda kembali ke sejarah penemuan Spread Spectrum. Spread Spectrum awalnya dikembangkan oleh seorang aktris Hedy Lamarr, ya, Anda tidak salah dengar, Hedy Lamarr adalah seorang aktris, seorang ikon yang menjadi lambang seksualitas di jamannya.

HedyLamarr
Gambar 1. Hedy Lamarr penemu Spread Spectrum

Spread Spectrum dipatenkan oleh Hedy Lamarr sebagai radio yang tidak dapat di-kacaukan atau dipasukan musuh, karena dikembangkan pada zaman perang. Memang ini adalah kelebihan dari Spread Spectrum, karena menggunakan bandwidth yang lebar dan sinyal ini terlihat sebagai noise dengan radio FM biasa, bahkan pengiriman sinyal ini umumnya lebih rendah dari sinyal noise floor. Sistem ini dipatenkan sebagai komunikasi untuk militer di mana diperlukan komunikasi radio yang tidak dapat diterima oleh musuh ataupun diganggu oleh musuh.

Cara Kerja Spread Spectrum

Spread Spectrum menggunakan level transmisi daya yang serupa dengan sebuah sinyal narrow-band. Namun, perbedaannya adalah sinyal spread spectrum akan memiliki power spectral density yang lebih rendah untuk masing-masing frekuensi sehingga sinyal Spread Spectrum dapat beroperasi bersama dengan Narrow Band pada frekuensi yang sama tanpa interferensi.

Pada Spread Spectrum, terdapat istilah Pseudo Noise code yang membungkus sebuah sinyal transmisi spread-spectrum sehingga sinyal tersebut terlihat wide-band dan seperti noise. Karakteristik ini yang menyebabkan Spread Spectrum memiliki probabilitas rendah untuk di intercept, akibat Spread Spectrum sulit dideteksi oleh peralatan narrow band dan hanya dapat dibaca oleh peralatan Spread Spectrum yang memiliki kode pseudo noise yang sama.

psd.gif
Gambar 2. Spread Spectrum Signal vs Narrow Band Signal

Gambar 2 di atas menunjukkan representasi sebuah sinyal Spread Spectrum dibandingkan sinyal Narrow Band. Kedua nya dapat memiliki daya yang sama, hal ini terlihat jika kita melakukan integral terhadap frequency (sesuai teorema Parseval, integral dari kuadrat sebuah fungsi dalam domain waktu sama dengan integral dari kuadrat sebuah fungsi dalam domain frekuensi).

LoRa Radio Berbasis Spread Spectrum

Mereferensikan kembali teorema Shannon-Hartley, yang mendefinisikan kapasitas kanal maksimum dari sebuah kanal komunikasi yang memiliki bandwidth tertentu yang terdapat noise. Sesuai dengan turunan persamaan Shannon-Hartley:

\displaystyle \frac{N}{S} \approx \frac{B}{C}

Dari persamaan di atas dapat diketahui bahwa untuk nilai perbandingan Noise-to-Signal yang tetap, untuk menaikkan kemampuan mengirim data kecepatan tinggi tanpa error, dibutuhkan bandwidth yang lebih besar. Teknologi LoRa yang menggunakan spread spectrum ini adalah teknologi wide band, sehingga memakan bandwidth yang cukup besar. Pemakaian bandwidth yang besar ini juga mempersulit izin frekuensi untuk keperluan LoRa di Indonesia.

Informasi teknis lebih lanjut tentang LoRa dapat melihat artikel saya yang lain: LoRa Modulation Basics

Referensi

  1. https://www.eetimes.com/document.asp?doc_id=1271899
  2. https://www.semtech.com/uploads/documents/an1200.22.pdf

Diskursus Statistika tentang Hitung Cepat

Akhirnya, ada diskursus yang berarti oleh proponent dan opponent dari hasil Hitung Cepat. Beberapa masukan saya terima baik dari proponent maupun opponent, saya rasa masukan ini sehat demi terungkapnya kebenaran. Salah satu diskusi pertama yaitu mengenai simulasi 1000 TPS:

Menggunakan program yang sama dengan sedikit modifikasi (dapat dilihat update di https://github.com/josefmtd/kpu-data), saya coba running dengan 1000 TPS dan mendapatkan 10 grafik ini:

Dari kesepuluh grafik tersebut, margin of error masih di bawah 1.5%, ini merupakan temuan yang menarik dan patut kembali dicerna dan dianalisis. Menjadi salah satu to-do list yang menarik untuk akhir pekan depan.

Selain dari kubu pendukung Hitung Cepat, ada juga masukkan yang berharga dari argumen kontra Hitung Cepat.

Program yang saya buat masih jauh dari sempurna, random seed masih menggunakan default dari library Python yang saya gunakan. Program saya hanya mengambil tabulasi data pasangan 01 dan 02 tanpa mengambil informasi lengkap TPS, hal ini tidak ada di tabulasi dari berbagai sumber, hanya KawalPemilu dan KPU yang menunjukkan data primer.

Masukan oleh @RajaGuguk14 masuk ke to-do list saya dalam pembuatan program selanjutnya. Karena keterbatasan kemampuan saya dalam pemrograman Python, mungkin butuh seharian, mari kita lihat apa mungkin saya menyelesaikan di akhir pekan.

Kurang lebih saya akan melakukan hal ini:

  1. Mengambil data dari API KawalPemilu (karena di API mereka terdapat indikator kejanggalan TPS, sehingga data janggal bisa saya skip)
  2. Membuat fungsi random dengan nilai seed statik agar bisa diikuti oleh orang lain
  3. Mengambil link foto C1 dari KawalPemilu untuk setiap data TPS.
  4. Menghasilkan tabulasi data raw dalam penyajian simulasi Hitung Cepat.

Sekali lagi artikel singkat ini saya tulis dengan harapan diskursus sehat mengenai statistika tetap berlanjut dan bisa terungkap kebenaran di tengah kemelut Pemilihan Presiden ini. Terima kasih untuk siapapun saja yang sudah mau bergabung dalam diskusi ini.

Deciphering the Tweet: Kesalahan Rumus dan Syarat Quick Count

Tidak bosan saya menulis artikel tentang tweet ini, karena sepertinya belum ada jawaban dan klarifikasi. Mari kita bedah kembali tweet dari Dr. Ronnie Higuchi Rusli, dosen program pascasarjana Universitas Indonesia. Pembedahan ini dilakukan untuk menguji pernyataan Dr. Ronnie Rusli tentang kesahihan Quick Count.

twit2

Dr. Ronnie Higuchi Rusli juga menuliskan syarat Quick Count sebagai berikut:

SyaratQuickCount

Pembahasan Tujuh Statement Dr. Ronnie

Ada delapan hal yang disampaikan pada link Tweet di atas yaitu terdiri dari:

  1. Dua buah persamaan, satu persamaan untuk Margin of Error dan satu lagi persamaan jumlah sampel TPS
  2. Lima pernyataan tentang masing-masing variabel untuk mencapai syarat Quick Count yang benar
  3. Satu grafik perpotongan 2 kurva distribusi normal

Saya akan mengupas ketujuh hal di atas, kecuali grafik, di mana grafik kurang jelas sumbu X dan Y nya menyebabkan grafik tersebut menjadi sulit dimengerti.

Persamaan Jumlah Sampel TPS

Persamaan jumlah sampel TPS ini sudah saya bahas sebelumnya di Statistika 101: Ukuran Sampel untuk Data Proporsi, di mana saya membahas kesalahan dari persamaan ini. Namun sekali lagi kita anggap persamaan Dr. Ronnie benar sehingga kita dapat menggunakan persamaan:

\displaystyle n_{tps} = \frac{p(1-p)}{MoE/(Z_{99\%})^2} + \frac{p(p-1)}{N}

Persamaan Margin of Error

Persamaan Margin of Error yang disampaikan adalah persamaan dasar untuk menghitung Margin of Error berdasarkan Confidence Level dan Standard Deviation:

\displaystyle MoE = \bigg[\frac{S_D}{\sqrt{n_{tps}}}\bigg]Z_{99\%}

Kelima Pernyataan Syarat Quick Count

Standard Deviasi wajib 1 persen
Margin of Error (MoE) 0,02-0,03%
Nilai Koefisien Z_{99\%} harus terpenuhi
Probabilitas masing-masing sama 50%
Jumlah sampel TPS yang dipakai tepat

Mari kita uji persamaan kedua (Margin of Error) dengan kalimat pertama dan kedua di pernyataan syarat Quick Count yang dijabarkan oleh Dr. Ronnie:

\displaystyle MoE = \bigg[\frac{S_D}{\sqrt{n_{sd}}}\bigg]Z_{99\%}

Masukkan margin of error 0.02%, standard deviasi 1% dan Z_{99\%} = 2.58 harus terpenuhi.

\displaystyle 0.02\% = \bigg[\frac{1\%}{\sqrt{n_{sd}}}\bigg]2.58

\displaystyle n_{tps} = \bigg(\frac{1\%}{0.02\%}*2.58\bigg)^2 = 16641

Mari kita uji persamaan pertama dengan variabel yang sama, seharusnya kedua persamaan menghasilkan nilai yang sama

\displaystyle n_{tps} = \frac{p(1-p)}{MoE/(Z_{99\%})^2} + \frac{p(p-1)}{N}

\displaystyle n_{tps} = \frac{0.25}{0.0002/(2.58)^2} + \frac{(-0.25)}{809497} = 8320.5

Terlihat jelas hasil jumlah TPS di persamaan pertama dan persamaan kedua berbeda. Hal ini patut dipertanyakan, seharusnya persamaan pertama dan kedua menghasilkan nilai yang sama. Untuk pembanding dapat dilihat penggunaan persamaan yang saya turunkan di Statistika 101: Ukuran Sampel untuk Data Proporsi:

\displaystyle n_{tps} = \frac{z^2\hat{p}(1-\hat{p})}{e^2}

Persamaan ini dapat disebut juga persamaan Cochran.

\displaystyle n_{tps} = \frac{2.58^2(0.5)(0.5)}{0.01^2} = 16641

Mengetahui bahwa standard deviasi dari sebuah data proporsi sebagai berikut:

\displaystyle \sigma = \sqrt{pq} = \sqrt{0.5(0.5)} = 0.5

Maka dapat kembali dimasukkan kepada persamaan MOE:

\displaystyle 0.01\% = \bigg(\frac{50\%}{n_{tps}}\bigg)Z_{99\%}

\displaystyle n_{tps} = \bigg(\frac{50\%}{1\%}*2.58\bigg)^2 = 16641

Jelas terlihat bahwa persamaan dari Dr. Ronnie salah, karena kedua persamaan tersebut tidak menghasilkan nilai yang sama.

Kesalahan Perlu Diklarifikasi

Kesalahan pertama yang sudah saya bahas adalah kesalahan rumus, di mana harusnya hasil penurunan rumus jumlah sampel TPS adalah

\displaystyle n_{tps} = \frac{z^2\hat{p}(1-\hat{p})}{e^2}

Kesalahan kedua yang nampak pada pernyataan Dr. Ronnie adalah standard deviasi wajib 1% dan probabilitas masing-masing adalah 50%. Keduanya tidak kompatibel di mana jika kita memasukkan nilai probabilitas 50%, nilai standard deviasi adalah:

\sigma = \sqrt{\hat{p}\hat{q}} = \sqrt{(50\%)(1-50\%)} = 50\%

Dengan artikel ini, sekali lagi saya mohon kepada Dr. Ronnie Rusli untuk mengklarifikasi persamaan dan syarat yang dituliskan di depan khalayak umum agar tidak ada misinformasi.

Bagi Anda yang membaca artikel ini, mohon sampaikan dan mention Dr. Ronnie mengenai masalah ini, semoga beliau berkenan untuk memperbaiki dan memberikan penjelasan kepada masyarakat tentang syarat Quick Count yang benar.

Lagi, Salah Rumus

Kelanjutan dari beberapa artikel belakang yang sudah saya buat mengenai statistika, terutama tentang pengambilan jumlah sampel dan uji coba Stratified Random Sampling dengan menggunakan bahasa pemrograman Python. Saya berharap ada salah satu yang dapat sampai ke Dr. Ronnie Rusli agar beliau dapat memperbaiki kesalahan di rumus nya. Kembali beliau memposting rumus ini:

twit2
Gambar 1. Tweet Dr. Ronnie Rusli mengenai pengambilan jumlah sampel Quick Count

Penggunaan Rumus Dr. Ronnie

Melihat sekilas rumus Dr. Ronnie, terlihat janggal, saya akan menjabarkan, jika saya memasukkan masing-masing variabel sesuai dengan kondisi kenyataan jumlah TPS di Indonesia dan Margin of Error yang diclaim telah dicapai oleh salah satu lembaga survey, yakni 1%.

n_{tps} adalah jumlah sampel yang dibutuhkan
p adalah estimasi persentase pasangan presiden wakil presiden
MoE adalah margin of error yang diinginkan
N adalah jumlah populasi TPS

Penjelasan ini juga dielaborasi sebelumnya oleh tweet Dr. Ronnie Rusli pada Gambar 2.

Capture
Gambar 2. Tweet Dr. Ronnie tentang jumlah sampel Quick Count dan penjelasan variabelnya

Memasukkan rumus di atas dengan nilai-nilai yang kita ketahui maka kita dapat menghitung sendiri jumlah TPS yang diperlukan berdasarkan Margin of Error (1%) dan jumlah TPS populasi (809.497). Asumsi sebelum dilakukan pemilihan adalah peluang masing-masing pasangan adalah sama, yakni 50%.

Rumus yang digunakan oleh Dr. Ronnie Rusli adalah n = [p(1-p)]/[(MoE)/(z99)^2)] + [p(p-1)/N]

Atau jika saya tuliskan dengan LaTeX agar lebih mudah terlihat dalam bentuk pecahan:

\displaystyle n_{tps} = \frac{p(1-p)}{\displaystyle \frac{MoE}{(z_{99\%})^2}} + \frac{p(p-1)}{N}

Masukkan variabel-variabel di atas, maka hasilnya akan menjadi

\displaystyle n_{tps} = \frac{0.5(1-0.5)}{\displaystyle \frac{0.01}{2.58^2}} + \frac{0.5(0.5-1)}{809497} = 166.41 + (-3.08*10^{-7}) \approx 166

Jika Anda tidak percaya dengan hitungan saya, sila coba dengan kalkulator Google, langsung masukkan nilai p, MoE, z, dan N pada rumus langsung dari Dr. Ronnie:

[0.5(1-0.5)]/[(0.01)/(2.58)^2)] + [0.5(0.5-1)/809497]
calculator
Gambar 3. Hasil perhitungan dengan kalkulator sesuai dengan rumus Dr. Ronnie

Rumus Jumlah Sampel Berdasarkan Penurunan Rumus

Semenjak SMP, SMA, maupun memasuki bangku kuliah, saya sangat senang dalam menurunkan rumus. Penurunan rumus memerlukan ketelitian, dan dalam matematika, ketelitian sangat penting. Hobby penurunan rumus ini membuat saya menjadi tertarik untuk menurunkan rumus berdasarkan nilai standard error proporsi dan nilai MoE berdasarkan tabel Z. Penurunan rumus saya dapat dilihat pada artikel sebelumnya Statistika 101: Ukuran Sampel untuk Data Proporsi.

\displaystyle n_{tps} = \frac{N}{\displaystyle 1 + \frac{MOE^2(N-1)}{z^2(\hat{p}(1-\hat{p})}}

Persamaan di atas adalah hasil akhir yang saya temukan, jika memasukkan nilai pada persamaan di atas:

\displaystyle n_{tps} = \frac{809497}{\displaystyle 1 + \frac{(0.01)^2(809497-1)}{(2.58)^2(0.5(1-0.5)}} \approx 16306

Addendum dan Opini

Melihat kesalahan sederhana pada rumus Dr. Ronnie Rusli, dapat terlihat bahwa ribuan warganet tidak kritis dan masih mempercayai semua hal yang dikatakan ahli. Hampir tidak ada yang berhenti untuk berpikir, apakah ini benar? Padahal aljabar sudah dipelajari sejak SD ataupun SMP, dan Indonesia menerapkan wajib belajar sampai SMA. Ribuan cuitan ulang (re-tweet) maupun like namun mereka tidak melihat persamaan dan mengujinya langsung.

Namun perlu diperhatikan bahwa walaupun rumus yang di sampaikan oleh Dr. Ronnie salah, namun menurut persamaan yang saya turunkan berdasarkan random sampling dan nilai proporsi, saya dapatkan jumlah TPS sampel sebesar 16rb, jauh di atas nilai 3000 maupun 5000 yang dipakai oleh lembaga survey.

Statistika 101: Ukuran Sampel untuk Data Proporsi

Background

Saya menulis artikel singkat ini tentang ukuran sampel dan data proporsi karena melihat rumus yang beredar oleh Dr. Ronnie Rusli tentang rumus Quick Count, menurut saya rumus ini memiliki permasalahan, seperti yang saya jabarkan di artikel sebelumnya: https://josefmtd.com/2019/05/04/statistika-101-memberikan-pengertian-hasil-quick-count-pilpres-2019/

Rumus yang disampaikan Dr. Ronnie di-retweet oleh 2778 akun dan diberi like oleh 4808 akun. Menurut saya keanehan ini perlu dijawab dengan sebuah artikel dan klarifikasi, maka saya menulis kembali artikel ini.

Artikel ini juga bertujuan untuk merapihkan catatan statistik dari mata kuliah Probabilitas dan Stokastik yang saya terima pada tanggal 25 Maret 2015, diampu oleh Bapak M. Firdaus S. Lubis, S.T., M.T., sekaligus memberikan contoh kasus pada sebuah data proporsi yakni dalam aplikasi Quick Count.

Central Limit Theorem dan Distribusi Normal

Dasar dari Random Sampling adalah Central Limit Theorem. Central Limit Theorem adalah sebuah teori statistik di mana jika diambil banyak sampel dari sebuah populasi dengan variansi data yang berhingga (finite), mean dari sampel yang diambil pada populasi yang sama akan sesuai dengan mean dari populasi. Central Limit Theorem menunjukkan bahwa semakin bertambahnya jumlah sampel yang diambil secara acak, maka distribusi kemungkinan letak nilai mean dari sampel tersebut akan mengikuti distribusi normal.

normaldistribution
Gambar 1. Persebaran mean dengan distribusi normal

Asumsi distribusi normal dapat kita pakai ketika jumlah sampel yang kita ambil sudah mencapai batas nilai tertentu. Jika distribusi normal dapat dicapai dengan sampel yang kita ambil, maka persamaan-persamaan ini menjadi valid:

\displaystyle S_x = \frac{\sigma_x}{\sqrt{n}}

S_x adalah standard error dari mean
\sigma_x adalah standard deviasi
n adalah jumlah sampel (tps)

\displaystyle moe = z(S_x)

moe adalah margin of error
z adalah nilai dari tabel distribusi normal sesuai dengan Confidence Level
S_x adalah standard error dari mean

Data Proporsi

Data proporsi menunjukkan perbandingan atau persentase dari sebuah populasi dengan karakteristik tertentu. Data proporsi ini memiliki kemungkinan binomial, antara ya atau tidak. Hal ini dapat diaplikasikan juga pada polling maupun quick count karena data yang diambil merepresentasikan persentase dipilihnya suatu kandidat atau partai politik. Nilai p (proportion of interest dari populasi) ini yang ingin dihitung dengan cepat dengan sampel untuk mendapatkan estimasi nilai p: \hat{p}. Untuk mendapatkan simpangan baku (standard deviasi) dari data hasil proporsi ini, kita perlu mengetahui proporsi populasi sesungguhnya, hal ini tidak memungkinkan pada aplikasi Quick Count, umumnya rumus standard error dari proporsi yang dipakai:

\displaystyle S_{\hat{p}} = \sqrt{\frac{\hat{p}(1-\hat{p})}{n}}

S_{\hat{p}} adalah standard error dari estimasi proporsi hasil sampel
\hat{p} adalah estimasi proporsi hasil sampel

Proporsi adalah data dengan distribusi binomial, namun seiring dengan bertambahnya sampel acak yang diambil pada populasi yang sama, maka distribusi kemungkinan nilai \hat{p} akan menuju distribusi normal, sehingga rumus sebelumnya kembali dapat dipakai.

\displaystyle moe_{\hat{p}} = z(S_{\hat{p}})

moe_{\hat{p}} adalah margin of error dari estimasi proporsi hasil sampel

Untuk memastikan bahwa sampel yang kita ambil sudah cukup untuk mendekati distribusi normal terdapat beberapa rule of thumb sebagai berikut:

\displaystyle np > 5
\displaystyle nq > 5

Mengambil Jumlah Sampel

Mengetahui syarat-syarat untuk mencapai jumlah sampel yang tepat, yaitu data proporsi (data binomial) harus merupakan sampel acak dengan jumlah yang dapat memenuhi syarat np > 5 dan nq > 5. Setelah itu dapat digunakan persamaan yang merupakan substitusi nilai S_{\hat{p}} pada persamaan moe_{\hat{p}} dan mengubah fungsi untuk mendapatkan nilai n maka didapatkan rumus:

\displaystyle n = \frac{\displaystyle z^2 [\hat{p}(1-\hat{p})]}{\displaystyle moe_{\hat{p}}^2}

Menggunakan persamaan ini dapat dihasilkan jumlah sampel yang sesuai dengan dasar Central Limit Theorem bahwa sampel memiliki distribusi normal untuk random sampling, dan kaidah binomial dan pendekatannya menuju distribusi normal jika sampel memadai.

Efek Populasi Berhingga terhadap Jumlah Sampel

Rumus di atas adalah rumus yang didefinisikan untuk sebuah populasi yang tak berhingga (infinite), namun pada kondisi riil, atau dalam konteks Pemilu, jumlah populasi TPS adalah berhingga, sehingga terdapat faktor pengali koreksi terhadap populasi yang berhingga pada standard deviasi. Hal ini disebabkan oleh pilihan sampel TPS tidak boleh overlap dari keseluruhan TPS populasi, (sampling without replacement). Hal ini menyebabkan nilai proporsi yang diambil menjadi dependen terhadap jumlah sampel dan populasi, sehingga standard error dari sampling perlu dikalikan dengan faktor koreksi populasi:

\displaystyle S_{\hat{p} finite N} = S_{\hat{p}} \sqrt{\frac{N_{tps}-n_{tps}}{N_{tps}-1}}

S_{\hat{p} finite N} adalah standard error sampling pada populasi berhingga
N_{tps} adalah jumlah populasi
n_{tps} adalah jumlah sampel

Perlu digarisbawahi bahwa rumus ini hanya berlaku jika sampel yang diambil sudah melebihi 5% dari populasi, jika tidak, nilai faktor koreksi ini akan mendekati 1, sehingga pengaruhnya tidak lagi besar.

Menurunkan rumus untuk mencari jumlah TPS (n_{tps} dengan pengaruh jumlah populasi ini didapatkan:

\displaystyle MOE = z \sqrt{\frac{\hat{p}(1-\hat{p})}{n}}\sqrt{\frac{N-n}{N-1}}

Kuadratkan kedua sisi menjadi:

\displaystyle MOE^2 = z^2 \frac{\hat{p}(1-\hat{p})}{n} \frac{N-n}{N-1}

Lalu tukar posisi masing-masing variabel sehingga berbentuk:

\displaystyle \frac{N-n}{n} = \frac{(N-1)(MOE)^2}{\hat{p}(1-\hat{p})z^2}

Lalu ubah persamaan di atas sehingga dapat menghasilkan fungsi n_{tps}

\displaystyle n_{tps} = \frac{N}{\displaystyle 1 + \frac{MOE^2(N-1)}{z^2 \big( \hat{p}(1-\hat{p} \big)}}

Persamaan di atas adalah persamaan yang mungkin harusnya di unggah dan di cuit oleh Dr. Ronnie Higuchi Rusli.

EDIT: Penambahan penurunan rumus pencarian jumlah sampel TPS

Appendix: Postingan Dr. Ronnie Rusli

capture.png

Statistika 101: Memberikan Pengertian Hasil Quick Count Pilpres 2019

Disclaimer: Saya bukan orang praktisi statistik maupun orang yang memiliki background di bidang statistik. Saya adalah lulusan S1 Teknik Elektro yang pada kurikulumnya hanya mendapatkan mata kuliah “Probabilitas dan Stokastik” di mana topik statistik hanya dibahas oleh dosen pengampu saya dalam 3 kali pertemuan sebagai pengantar sebelum masuk materi kuliah tentang Probability Density Function

Terlalu Panjang; Tidak Membaca (TL;DR): Dugaan pak Ronnie Rusli terhadap kebutuhan sampel yang lebih banyak dari 2000 untuk mencapai margin of error serendah 1% memiliki dasar yang valid berdasarkan teori pengambilan sampel Random Sampling. Metode Stratified Random Sampling dengan 2000 sampel menghasilkan Margin of Error di bawah 1% menurut percobaan dari Penulis. Dengan Margin of Error 1% maupun 2% tetap terdapat jarak lebih besar dari 2 kali Margin of Error untuk kedua metode, sehingga tidak bisa dipungkiri bahwa Quick Count dapat memprediksikan bahwa pasangan ’01’ menang.

Background

Mengapa saya menulis blog post ini? Hal ini saya lakukan karena sebagai alumni Universitas Indonesia, terketuk hati dengan pernyataan pak Ronnie Rusli yang mempertanyakan sampling dari juragan survey Indonesia. Saya dan teman juga skeptis tentang kehebatan juragan survey yang mampu menghasilkan data cukup akurat dengan hanya 2000 data dengan margin of error yang sebesar 1%.

Ramai di jagat media sosial bahwa mereka harus dipertemukan dengan suasana debat akademis. Namun, menurut saya kurang tepat jika hal ini diselesaikan dengan debat akademis, permasalahan ini adalah permasalahan matematika dan dengan mudah dapat diselesaikan dengan perhitungan. Banyak alat yang dapat digunakan pada zaman ini seperti pemrograman Python untuk melakukan simulasi hitungan tanpa perlu menggunakan berlembar-lembar kertas dan pulpen.

Seperti kata banyak orang, “Show, don’t tell“, maka saya akan menunjukkan simulasi perhitungan Quick Count dengan metode Stratified Random Sampling dengan Python. Anda pun dapat mengikuti apa yang saya kerjakan karena saya akan membuka datanya di GitHub saya: https://github.com/josefmtd/kpu-data/

Margin of Error dari Random Sampling

Untuk penjelasan lebih lanjut tentang Margin of Error dan Standard Error, Anda dapat melihat di blog oleh dosen Teknik Industri UI: Bapak Komarudin https://staff.blog.ui.ac.id/komarudin74/mengotak-atik-statistik-quick-count-pilpres-2014/

Seperti dari coretan bapak Ronnie Rusli, margin of error didefinisikan dengan:

\displaystyle SE = \frac{SD}{\sqrt{n}}

\displaystyle MOE = z(SE)

MOE adalah Margin of Error, SD adalah Standard Deviation, n adalah jumlah sample, dan z adalah nilai critical yang diambil dari tabel F(z) sesuai dengan nilai confidence level yang kita inginkan. Untuk confidence level 99%, kita menggunakan nilai z = 2.58.

Untuk mencari sample size dari sebuah data pilihan biner seperti Pemilu yakni antara 01 dan 02, maka dapat digunakan rumus Standard Error khusus untuk proporsi yakni:

\displaystyle SE_p = \sqrt{\frac{p(1-p)}{n}}

dengan menggabungkan rumus di atas dengan rumus MOE dan mengubahnya untuk mencari jumlah sampel (n) maka didapatkan persamaan:

\displaystyle n = \frac{z^2(p(1-p))}{(MOE)^2}

p adalah persentase suara 01 dan (1-p) merepresentasikan suara 02, karena sebelum sampling kita tidak bisa menebak persentase suara, kita dapat masukkan nilai 50% untuk p dan 50% untuk (1-p).

\displaystyle n = \frac{ (2.58)^2 (50\%) (50\%) }{(1\%)^2} = 16641 samples

\displaystyle n = \frac{ (2.58)^2 (50\%) (50\%) }{(0.5\%)^2} = 66564 samples

Dengan persamaan ini kita dapat membuktikan secara teori bahwa untuk menghasilkan data dengan Margin of Error sebesar 1% perlu 16641 sampel dan 0.5% perlu 66564 sampel. Sementara Quick Count Indikator dengan sampel 2975 disebut-sebut dapat menghasilkan Margin of Error sebesar 0.63%, dan Quick Count SMRC dengan sampel 5907 TPS dapat menghasilkan Margin of Error sebesar 0.5%. Hal ini tentu berkontradiksi dengan persamaan yang saya tunjukkan di atas. Saya dan teman saya, setuju dengan pak Ronnie Rusli yang skeptis dengan nilai sampel yang sangat sedikit dapat menghasilkan Margin of Error yang sangat rendah.

Namun, perlu digarisbawahi juga sikap skeptis ini berdasarkan teori statistik Random Sampling. Sikap skeptis ini bukan karena pendirian partisan, namun karena dasar ilmiah. Teman sayapun berbicara: “Always question your results“, maka saya mencoba melakukan Random Sampling dengan Python untuk membuktikan apakah benar lembaga survey dapat menghasilkan margin of error serendah itu dengan sampel 2000 TPS?

UPDATE 05 May: Bapak Doktor Ronnie Rusli mengeluarkan rumus yang digunakan untuk mendapatkan sampel:

\displaystyle n_{tps} = \frac{[p(1-p)]}{\displaystyle \frac{MOE}{z_{99\%}^2} + \frac{p(1-p)}{N}}

\displaystyle n_{tps} = \frac{25\%}{\displaystyle \frac{1\%}{2.58^2} + \frac{25\%}{809000}} = 166

Capture.PNG

Hasilnya malah melebihi jumlah TPS, sepertinya ada yang salah dengan rumus pak Doktor Ronnie Rusli, saya tetap akan mengambil dari rumus yang saya cantumkan, berasal dari catatan statistik saya dan juga sama dengan yang ditulis oleh bapak dosen Komarudin.  Terdapat typo dalam input kalkulator sehingga  hasil menjadi salah, persamaan Dr. Ronnie menghasilkan nilai 166 TPS

Menggunakan nilai sampel TPS dengan persamaan oleh Dr. Ronnie, hasilnya adalah 166 TPS, hasil ini malah lebih rendah dari 2000 TPS. Pasti ada kesalahan di persamaan

Intermezzo: Distribusi Suara Nasional

Keskeptisan saya tidak hanya berada pada jumlah sampel, tapi juga terhadap distribusi suara tiap-tiap TPS per provinsi, apakah datanya terdistribusi normal? Seperti yang sudah dibahas di blog bapak dosen FTUI di atas, penggunaan rumus-rumus dalam Quick Count dengan Random Sampling, menggunakan asumsi bahwa data terdistribusi normal, dengan Pilpres yang terlalu terpolarisasi seperti ini, apakah benar Pilpres menghasilkan data distribusi Normal? Saya ragu, sehingga saya mengambil data untuk mencari tahu.

Keraguan saya, saya luapkan dengan code sederhana dalam Python seperti ini (dapat diakses di github.com/josefmtd/kpu-data/distributionPlot.py):

Tekan Spoiler ini untuk menunjukkan code distributionPlot.py
# -*- coding: utf-8 -*-
"""
DistributionPlot.py
Created on Fri May 3 20:35:56 2019

@author: JosefStevanus
"""

import os
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

directory = 'C:/Users/JosefStevanus/Documents/GitHub/kpu-data/'
data = {}
cleanNolSatuNasional = []
cleanNolDuaNasional = []
cleanTotalSuaraNasional = []

for filename in os.listdir(directory):
if filename.endswith(".csv"):
dataName = filename[:-4]
data[dataName] = pd.read_csv(filename)

nolSatu = data[dataName]["01 KPU"] 
nolDua = data[dataName]["02 KPU"]
cleanNolSatu = []
cleanNolDua = []

for x in range(len(nolSatu)):
if nolSatu[x] == 0 and nolDua[x] == 0:
continue
else:
cleanNolSatu.append(nolSatu[x])
cleanNolDua.append(nolDua[x])

cleanTotalSuara = np.add(cleanNolSatu, cleanNolDua)

cleanNolSatuNasional += cleanNolSatu
cleanNolDuaNasional += cleanNolDua
cleanTotalSuaraNasional += cleanTotalSuara.tolist() 

percentageNolSatu = np.divide(cleanNolSatu, cleanTotalSuara)*100
percentageNolSatuSeries = pd.Series(percentageNolSatu,
name = "Distribusi Persentase Suara 01 di "+dataName )

plotDistribusi, seaborn = plt.subplots()
sns.distplot(percentageNolSatuSeries, kde=False, bins=100, ax=seaborn)
plotDistribusi.savefig('C:/Users/JosefStevanus/Documents/GitHub/kpu-data/'+dataName+".png")
continue
else:
continue

percentageNolSatuNasional = np.divide(cleanNolSatuNasional, cleanTotalSuaraNasional)*100
percentageNolSatuNasionalSeries = pd.Series(percentageNolSatuNasional,
name = "Distribusi Persentase Suara 01 Nasional")

plotDistribusiNasional, seabornNasional = plt.subplots()
sns.distplot(percentageNolSatuNasionalSeries, kde=False, bins=100, ax = seabornNasional)
plotDistribusiNasional.savefig("C:/Users/JosefStevanus/Documents/GitHub/kpu-data/Nasional.png")

 

Hasilnya saya mendapatkan distribusi suara 01 diseluruh TPS di Indonesia seperti pada Gambar 1. Grafik histogram pada Gambar 1 menunjukkan distribusi yang sama sekali tidak terlihat seperti distribusi normal. Hal ini dapat dimaklumkan karena banyaknya provinsi yang tidak memiliki distribusi normal, contohnya pada Gambar 2 yang menunjukkan distribusi persentase suara di provinsi yang sangat mengunggulkan salah satu pihak (01 atau 02), atau pada Gambar 3 dimana distribusi suara menunjukkan persebaran suara yang terpolarisasi, di mana ada dua nilai peak pada 01 maupun 02.

Nasional
Gambar 1. Distribusi Persentase Suara 01 di Seluruh TPS di Indonesia
heavilySkewed
Gambar 2. Distribusi Persentase Suara 01 di Seluruh TPS di Provinsi (atas kiri) Aceh, (atas kanan) Bali, (bawah kiri) NTT, (bawah kanan) Papua Barat
polarized.png
Gambar 3. Distribusi Persentase Suara 01 di Seluruh TPS di Provinsi (atas kiri) Sumatera Utara, (atas kanan) Maluku Utara, dan (bawah kiri) Kalimantan Barat

Data yang sangat tidak lazim ini menyebabkan saya semakin skeptis dengan metode Quick Count yang dilaksanakan, karena semakin skeptis, maka saya mulai menulis program singkat dengan Random Sampling dan Stratified Random Sampling.

Random Sampling

Rumus yang saya gunakan di awal adalah rumus untuk mencari jumlah sampel Random Sampling. maka program pertama yang saya buat adalah program Random Sampling menggunakan data sementara dari Situng KPU menggunakan program Python, data saya saya ambil oleh hasil crawling data oleh bapak Yohanda Mandala (https://twitter.com/jokidz90).  Data ini terakhir di update oleh beliau pada tanggal 3 Mei jam 08.06 pagi.

Tekan Spoiler ini untuk melihat code RandomSampling.py
# -*- coding: utf-8 -*-
"""
RandomSampling.py
Created on Sat May 4 09:45:12 2019

@author: DarwinHarianto
"""

import os
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import random

directory = 'C:/Users/JosefStevanus/Documents/GitHub/kpu-data/'
data = {}
cleanNolSatuNasional = []
cleanNolDuaNasional = []
cleanTotalSuaraNasional = []

for filename in os.listdir(directory):
if filename.endswith(".csv"):
dataName = filename[:-4]
data[dataName] = pd.read_csv(filename)

nolSatu = data[dataName]["01 KPU"] 
nolDua = data[dataName]["02 KPU"]
cleanNolSatu = []
cleanNolDua = []

for x in range(len(nolSatu)):
if nolSatu[x] == 0 and nolDua[x] == 0:
continue
else:
cleanNolSatu.append(nolSatu[x])
cleanNolDua.append(nolDua[x])

cleanTotalSuara = np.add(cleanNolSatu, cleanNolDua)

cleanNolSatuNasional += cleanNolSatu
cleanNolDuaNasional += cleanNolDua
cleanTotalSuaraNasional += cleanTotalSuara.tolist() 
continue
else:
continue

percentageNolSatuNasional = np.divide(cleanNolSatuNasional, cleanTotalSuaraNasional)*100
percentageNolSatuNasionalSeries = pd.Series(percentageNolSatuNasional,
name = "Distribusi Persentase Suara 01 Nasional")

plotDistribusiNasional, seabornNasional = plt.subplots()
sns.distplot(percentageNolSatuNasionalSeries, kde=False, bins=100, ax = seabornNasional)
plotDistribusiNasional.savefig("C:/Users/JosefStevanus/Documents/GitHub/kpu-data/Nasional.png")

randomSample = random.sample(range(len(cleanTotalSuaraNasional)), 2000)

nolSatuRandomSample = []
nolDuaRandomSample = []
totalSuaraRandomSample = []

for x in randomSample:
    nolSatuRandomSample.append(cleanNolSatuNasional[x])
    nolDuaRandomSample.append(cleanNolDuaNasional[x])
    totalSuaraRandomSample.append(cleanTotalSuaraNasional[x])

percentageNolSatuRandomSample = np.divide(nolSatuRandomSample, totalSuaraRandomSample)*100
percentageNolSatuRandomSampleSeries = pd.Series(percentageNolSatuRandomSample, 
                                                name = "Distribusi Persentase Suara 01 Random Sample")

meanNolSatu = np.sum(nolSatuRandomSample)/np.sum(totalSuaraRandomSample)*100
meanNolDua = 100 - meanNolSatu
standardError = np.std(percentageNolSatuRandomSample)/np.sqrt(len(randomSample))

meanNolSatuNasional = np.sum(cleanNolSatuNasional)/np.sum(cleanTotalSuaraNasional)*100
meanNolDuaNasional = 100 - meanNolSatuNasional
standardErrorNasional = np.std(percentageNolSatuNasional)/np.sqrt(len(cleanTotalSuaraNasional))

plotDistribusiNasionalRandomSample, ax = plt.subplots(2,1)
sns.distplot(percentageNolSatuRandomSampleSeries, kde=False, bins=100, ax = ax[0])
sns.distplot(percentageNolSatuNasionalSeries, kde=False, bins=100, ax = ax[1])
plotDistribusiNasionalRandomSample.suptitle('{:.2f}% vs {:.2f}% Real Count Sementara \n {:.2f}% vs {:.2f}% Random Sampling dengan MOE = +/-{:.2f}%'.format(meanNolSatuNasional, meanNolDuaNasional, meanNolSatu, meanNolDua, standardError*2.58))
plotDistribusiNasionalRandomSample.savefig('RandomSampling.png')

 

Dalam mengambil sampel random dari Data Seluruh TPS Nasional yang saya dapatkan, saya menggunakan fungsi random yang ada di Python. Saya cukup percaya bahwa komputer tidak memihak 01 maupun 02. Untuk meyakinkan Anda, saya juga menunjukkan distribusi sampel yang diambil oleh program dan dibandingkan dengan distribusi populasi (yakni dari Situng KPU). Selain itu program saya juga menghitung mean persentase 01 dan 02, dan mencari nilai standard error dan margin of error nya. Semua hasil ditunjukkan dalam 1 grafik yang menunjukkan distribusi dan nilai Random Sampling terhadap Real Count.

RandomSampling4
Gambar 4. Hasil Random Sampling 2000 TPS dari seluruh TPS di Indonesia sementara (456.135 TPS)

Karena menggunakan Python, saya dapat dengan bebas mengambil beberapa kali Random Sampling sesuka saya, dengan mengambil data yang berbeda-beda. Hasilnya dapat dilihat pada Gambar 5.

randomSamplingData.png
Gambar 5. Beberapa Hasil Random Sampling 2000 TPS

Gambar 5 memperkuat pernyataan bapak Ronnie Rusli bahwa 2000 TPS jelas-jelas tidak cukup untuk menghasilkan Margin of Error dibawah 1.5%, bisa saja saya minta program itu berjalan 1000 kali lagi, saya yakin hasilnya pasti tidak jauh berbeda dari 1.5%.

“Insanity is doing the same thing and expecting a different result”, kata Albert Einstein

Perlu digarisbawahi bahwa Quick Count dari berbagai lembaga survey ini menggunakan Stratified Clustered Random Sampling, di mana suara individu direpresentasikan dalam TPS, dan jumlah TPS yang diambil per provinsi disesuaikan dengan jumlah proporsi penduduknya, sehingga kembali saya mempertanyakan hasil Random Sampling ini dan membuat program Stratified Random Sampling

Stratified Random Sampling

Lembaga survey sudah membagikan laporan quick count mereka, di situ saya dapat melihat berapa jumlah TPS yang mereka pakai, dengan mengikuti proporsi yang mereka gunakan, saya mengambil sampel TPS berdasarkan proporsi jumlah TPS provinsi tersebut dibandingkan TPS seluruh Indonesia.

Mereka menuliskan rumus quick count untuk mendapatkan persentase nilai 01 atau 02 dan juga nilai margin of errornya. Berikut yang ditampilkan oleh lembaga survey tersebut:

\displaystyle \hat{p} = \frac{\displaystyle \sum_{h=1}^{H} \sum_{i=1}^{n_h} \frac{N_h}{n_h}y_{hi}}{\displaystyle \sum_{h=1}^{H} \sum_{i=1}^{n_h} \frac{N_h}{n_h}x_{hi}}

\displaystyle moe = 2(SE) = 2 \sqrt{\frac{1}{\hat{X}^2}\sum_{h=1}^{H} \frac{N_h(N_h - n_h)}{n_h} \hat{var_h}}

Kedua persamaan itu saya realisasikan dalam program Python sebagai berikut:

Tekan Spoiler ini untuk melihat code StratifiedRandomSampling.py
# -*- coding: utf-8 -*-
"""
StratifiedRandomSampling.py
Created on Sat May 4 09:45:12 2019

@author: JosefStevanus
"""

# -*- coding: utf-8 -*-
"""
Created on Fri May 3 20:35:56 2019

@author: JosefStevanus
"""

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import random

class dataPemilu:
def __init__(self, filename, dataSource, totalTPS, sampleSize):
self.totalTPS = totalTPS
self.sampleSize = sampleSize
self.nolSatu = []
self.nolDua = []
self.totalSuara = []
self.sampleNolSatu = []
self.sampleNolDua = []
self.sampleTotalSuara = []
self.nolSatuQuickCount = 0
self.nolDuaQuickCount = 0
self.totalSuaraQuickCount = 0
self.varianceQuickCount = 0
self.filename = filename
self.getRawData(dataSource)
self.cleanData()
self.getQuickCountData()

def getRawData(self, dataSource):
data = pd.read_csv(self.filename)
if dataSource == "KPU":
self.nolSatu = data["01 KPU"]
self.nolDua = data["02 KPU"]
elif dataSource == "KawalPemilu":
self.nolSatu = data ["01 KawalPemilu"]
self.nolDua = data ["02 KawalPemilu"]
else:
print("Source data does not exist")
self.totalSuara = np.add(self.nolSatu, self.nolDua)

def cleanData(self):
cleanNolSatu = []
cleanNolDua = []
for x in range (len(self.totalSuara)):
if self.nolSatu[x] == 0 and self.nolDua[x] == 0:
continue
else:
cleanNolSatu.append(self.nolSatu[x])
cleanNolDua.append(self.nolDua[x])

self.nolSatu = cleanNolSatu
self.nolDua = cleanNolDua
self.totalSuara = np.add(cleanNolSatu, cleanNolDua).tolist()

def randomSample(self):
listSample = random.sample(range(len(self.totalSuara)), self.sampleSize)
for x in listSample:
self.sampleNolSatu.append(self.nolSatu[x])
self.sampleNolDua.append(self.nolDua[x])
self.sampleTotalSuara.append(self.totalSuara[x])

def getQuickCountData(self):
self.randomSample()
self.nolSatuQuickCount = np.sum(self.sampleNolSatu)*self.totalTPS/self.sampleSize
self.nolDuaQuickCount = np.sum(self.sampleNolDua)*self.totalTPS/self.sampleSize
self.totalSuaraQuickCount = np.sum(self.sampleTotalSuara, dtype=np.float64)*self.totalTPS/self.sampleSize # needs 64 bit to prevent overflow

def getVariance(self, proporsi):
vhi = np.subtract(self.sampleNolSatu, np.multiply(proporsi, self.sampleTotalSuara))
vh = np.sum(vhi)/self.sampleSize
variance = np.sum(np.square(vhi-vh))/(self.sampleSize-1)
varianceQuickCount = variance*self.totalTPS*(self.totalTPS-self.sampleSize)/self.sampleSize
return varianceQuickCount

sampleSize = 2000

totalTPSAceh = 17389
totalTPSBali = 12386
totalTPSBanten = 33471
totalTPSBengkulu = 6165
totalTPSDaerahIstimewaYogyakarta = 11781
totalTPSDaerahKhususIbukotaJakarta = 29063
totalTPSGorontalo = 5866
totalTPSJambi = 11342
totalTPSJawaBarat = 70169
totalTPSJawaTengah = 86121
totalTPSJawaTimur = 67454
totalTPSKalimantanBarat = 10791
totalTPSKalimantanSelatan = 6722
totalTPSKalimantanTengah = 4780
totalTPSKalimantanTimur = 6780
totalTPSKalimantanUtara = 1512
totalTPSKepulauanBangka = 3597
totalTPSKepulauanRiau = 4659
totalTPSLampung = 20407
totalTPSMaluku = 2705
totalTPSMalukuUtara = 3805
totalTPSNusaTenggaraBarat = 10082
totalTPSNusaTenggaraTimur = 7656
totalTPSPapua = 1006
totalTPSPapuaBarat = 749
totalTPSRiau = 13174
totalTPSSulawesiBarat = 1857
totalTPSSulawesiSelatan = 14608
totalTPSSulawesiTengah = 3719
totalTPSSulawesiTenggara = 3710
totalTPSSulawesiUtara = 3986
totalTPSSumateraBarat = 13804
totalTPSSumateraSelatan = 19427
totalTPSSumateraUtara = 30883
totalTPSNasional = 809497

aceh = dataPemilu("Aceh.csv", "KPU", totalTPSAceh, int(1.95/100*sampleSize))
bali = dataPemilu("Bali.csv", "KPU", totalTPSBali, int(1.53/100*sampleSize))
banten = dataPemilu("Banten.csv", "KPU", totalTPSBanten, int(4.13/100*sampleSize))
bengkulu = dataPemilu("Bengkulu.csv", "KPU", totalTPSBengkulu, int(0.76/100*sampleSize))
daerahIstimewaYogyakarta = dataPemilu("DaerahIstimewaYogyakarta.csv", "KPU", totalTPSDaerahIstimewaYogyakarta, int(1.46/100*sampleSize))
daerahKhususIbukotaJakarta = dataPemilu("DaerahKhususIbukotaJakarta.csv", "KPU", totalTPSDaerahKhususIbukotaJakarta, int(3.58/100*sampleSize))
gorontalo = dataPemilu("Gorontalo.csv", "KPU", totalTPSGorontalo, int(0.42/100*sampleSize))
jambi = dataPemilu("Jambi.csv", "KPU", totalTPSJambi, int(1.40/100*sampleSize))
jawaBarat = dataPemilu("JawaBarat.csv", "KPU", totalTPSJawaBarat, int(17.05/100*sampleSize))
jawaTengah = dataPemilu("JawaTengah.csv", "KPU", totalTPSJawaTengah, int(14.25/100*sampleSize))
jawaTimur = dataPemilu("JawaTimur.csv", "KPU", totalTPSJawaTimur, int(16.06/100*sampleSize))
kalimantanBarat = dataPemilu("KalimantanBarat.csv", "KPU", totalTPSKalimantanBarat, int(2.04/100*sampleSize))
kalimantanSelatan = dataPemilu("KalimantanSelatan.csv", "KPU", totalTPSKalimantanSelatan, int(1.34/100*sampleSize))
kalimantanTengah = dataPemilu("KalimantanTengah.csv", "KPU", totalTPSKalimantanTengah, int(1.00/100*sampleSize))
kalimantanTimur = dataPemilu("KalimantanTimur.csv", "KPU", totalTPSKalimantanTimur, int(1.34/100*sampleSize))
kalimantanUtara = dataPemilu("KalimantanUtara.csv", "KPU", totalTPSKalimantanUtara, int(0.27/100*sampleSize))
kepulauanBangka = dataPemilu("KepulauanBangka.csv", "KPU", totalTPSKepulauanBangka, int(0.47/100*sampleSize))
kepulauanRiau = dataPemilu("KepulauanRiau.csv", "KPU", totalTPSKepulauanRiau, int(0.67/100*sampleSize))
lampung = dataPemilu("Lampung.csv", "KPU", totalTPSLampung, int(3.24/100*sampleSize))
maluku = dataPemilu("Maluku.csv", "KPU", totalTPSMaluku, int(0.68/100*sampleSize))
malukuUtara = dataPemilu("MalukuUtara.csv", "KPU", totalTPSMalukuUtara, int(0.47/100*sampleSize))
nusaTenggaraBarat = dataPemilu("NusaTenggaraBarat.csv", "KPU", totalTPSNusaTenggaraBarat, int(1.98/100*sampleSize))
nusaTenggaraTimur = dataPemilu("NusaTenggaraTimur.csv", "KPU", totalTPSNusaTenggaraTimur, int(1.85/100*sampleSize))
papua = dataPemilu("Papua.csv", "KPU", totalTPSPapua, int(1.88/100*sampleSize))
papuaBarat = dataPemilu("PapuaBarat.csv", "KPU", totalTPSPapuaBarat, int(0.48/100*sampleSize))
riau = dataPemilu("Riau.csv", "KPU", totalTPSRiau, int(2.18/100*sampleSize))
sulawesiBarat = dataPemilu("SulawesiBarat.csv", "KPU", totalTPSSulawesiBarat, int(0.48/100*sampleSize))
sulawesiSelatan = dataPemilu("SulawesiSelatan.csv", "KPU", totalTPSSulawesiSelatan, int(3.25/100*sampleSize))
sulawesiTengah = dataPemilu("SulawesiTengah.csv", "KPU", totalTPSSulawesiTengah, int(1.13/100*sampleSize))
sulawesiTenggara = dataPemilu("SulawesiTenggara.csv", "KPU", totalTPSSulawesiTenggara, int(0.97/100*sampleSize))
sulawesiUtara = dataPemilu("SulawesiUtara.csv", "KPU", totalTPSSulawesiUtara, int(0.97/100*sampleSize))
sumateraBarat = dataPemilu("SumateraBarat.csv", "KPU", totalTPSSumateraBarat, int(2.06/100*sampleSize))
sumateraSelatan = dataPemilu("SumateraSelatan.csv", "KPU", totalTPSSumateraSelatan, int(3.13/100*sampleSize))
sumateraUtara = dataPemilu("SumateraUtara.csv", "KPU", totalTPSSumateraUtara, int(5.27/100*sampleSize))

## Real Count KPU

nolSatuNasional = aceh.nolSatu + bali.nolSatu + banten.nolSatu + bengkulu.nolSatu + daerahIstimewaYogyakarta.nolSatu + daerahKhususIbukotaJakarta.nolSatu + gorontalo.nolSatu + jambi.nolSatu + jawaBarat.nolSatu + jawaTengah.nolSatu + jawaTimur.nolSatu + kalimantanBarat.nolSatu + kalimantanSelatan.nolSatu + kalimantanTengah.nolSatu + kalimantanTimur.nolSatu + kalimantanUtara.nolSatu + kepulauanBangka.nolSatu + kepulauanRiau.nolSatu + lampung.nolSatu + maluku.nolSatu + malukuUtara.nolSatu + nusaTenggaraBarat.nolSatu + nusaTenggaraTimur.nolSatu + papua.nolSatu + papuaBarat.nolSatu + riau.nolSatu + sulawesiBarat.nolSatu + sulawesiSelatan.nolSatu + sulawesiTengah.nolSatu + sulawesiTenggara.nolSatu + sulawesiUtara.nolSatu + sumateraBarat.nolSatu + sumateraSelatan.nolSatu + sumateraUtara.nolSatu
nolDuaNasional = aceh.nolDua + bali.nolDua + banten.nolDua + bengkulu.nolDua + daerahIstimewaYogyakarta.nolDua + daerahKhususIbukotaJakarta.nolDua + gorontalo.nolDua + jambi.nolDua + jawaBarat.nolDua + jawaTengah.nolDua + jawaTimur.nolDua + kalimantanBarat.nolDua + kalimantanSelatan.nolDua + kalimantanTengah.nolDua + kalimantanTimur.nolDua + kalimantanUtara.nolDua + kepulauanBangka.nolDua + kepulauanRiau.nolDua + lampung.nolDua + maluku.nolDua + malukuUtara.nolDua + nusaTenggaraBarat.nolDua + nusaTenggaraTimur.nolDua + papua.nolDua + papuaBarat.nolDua + riau.nolDua + sulawesiBarat.nolDua + sulawesiSelatan.nolDua + sulawesiTengah.nolDua + sulawesiTenggara.nolDua + sulawesiUtara.nolDua + sumateraBarat.nolDua + sumateraSelatan.nolDua + sumateraUtara.nolDua
totalSuaraNasional = aceh.totalSuara + bali.totalSuara + banten.totalSuara + bengkulu.totalSuara + daerahIstimewaYogyakarta.totalSuara + daerahKhususIbukotaJakarta.totalSuara + gorontalo.totalSuara + jambi.totalSuara + jawaBarat.totalSuara + jawaTengah.totalSuara + jawaTimur.totalSuara + kalimantanBarat.totalSuara + kalimantanSelatan.totalSuara + kalimantanTengah.totalSuara + kalimantanTimur.totalSuara + kalimantanUtara.totalSuara + kepulauanBangka.totalSuara + kepulauanRiau.totalSuara + lampung.totalSuara + maluku.totalSuara + malukuUtara.totalSuara + nusaTenggaraBarat.totalSuara + nusaTenggaraTimur.totalSuara + papua.totalSuara + papuaBarat.totalSuara + riau.totalSuara + sulawesiBarat.totalSuara + sulawesiSelatan.totalSuara + sulawesiTengah.totalSuara + sulawesiTenggara.totalSuara + sulawesiUtara.totalSuara + sumateraBarat.totalSuara + sumateraSelatan.totalSuara + sumateraUtara.totalSuara

persentaseNolSatuNasional = np.divide(nolSatuNasional, totalSuaraNasional)*100

meanNolSatuNasional = np.sum(nolSatuNasional)/np.sum(totalSuaraNasional)*100
meanNolDuaNasional = 100 - meanNolSatuNasional

## Stratified Random Sampling

nolSatuQuickCountNasional = aceh.nolSatuQuickCount + bali.nolSatuQuickCount + banten.nolSatuQuickCount + bengkulu.nolSatuQuickCount + daerahIstimewaYogyakarta.nolSatuQuickCount + daerahKhususIbukotaJakarta.nolSatuQuickCount + gorontalo.nolSatuQuickCount + jambi.nolSatuQuickCount + jawaBarat.nolSatuQuickCount + jawaTengah.nolSatuQuickCount + jawaTimur.nolSatuQuickCount + kalimantanBarat.nolSatuQuickCount + kalimantanSelatan.nolSatuQuickCount + kalimantanTengah.nolSatuQuickCount + kalimantanTimur.nolSatuQuickCount + kalimantanUtara.nolSatuQuickCount + kepulauanBangka.nolSatuQuickCount + kepulauanRiau.nolSatuQuickCount + lampung.nolSatuQuickCount + maluku.nolSatuQuickCount + malukuUtara.nolSatuQuickCount + nusaTenggaraBarat.nolSatuQuickCount + nusaTenggaraTimur.nolSatuQuickCount + papua.nolSatuQuickCount + papuaBarat.nolSatuQuickCount + riau.nolSatuQuickCount + sulawesiBarat.nolSatuQuickCount + sulawesiSelatan.nolSatuQuickCount + sulawesiTengah.nolSatuQuickCount + sulawesiTenggara.nolSatuQuickCount + sulawesiUtara.nolSatuQuickCount + sumateraBarat.nolSatuQuickCount + sumateraSelatan.nolSatuQuickCount + sumateraUtara.nolSatuQuickCount
nolDuaQuickCountNasional = aceh.nolDuaQuickCount + bali.nolDuaQuickCount + banten.nolDuaQuickCount + bengkulu.nolDuaQuickCount + daerahIstimewaYogyakarta.nolDuaQuickCount + daerahKhususIbukotaJakarta.nolDuaQuickCount + gorontalo.nolDuaQuickCount + jambi.nolDuaQuickCount + jawaBarat.nolDuaQuickCount + jawaTengah.nolDuaQuickCount + jawaTimur.nolDuaQuickCount + kalimantanBarat.nolDuaQuickCount + kalimantanSelatan.nolDuaQuickCount + kalimantanTengah.nolDuaQuickCount + kalimantanTimur.nolDuaQuickCount + kalimantanUtara.nolDuaQuickCount + kepulauanBangka.nolDuaQuickCount + kepulauanRiau.nolDuaQuickCount + lampung.nolDuaQuickCount + maluku.nolDuaQuickCount + malukuUtara.nolDuaQuickCount + nusaTenggaraBarat.nolDuaQuickCount + nusaTenggaraTimur.nolDuaQuickCount + papua.nolDuaQuickCount + papuaBarat.nolDuaQuickCount + riau.nolDuaQuickCount + sulawesiBarat.nolDuaQuickCount + sulawesiSelatan.nolDuaQuickCount + sulawesiTengah.nolDuaQuickCount + sulawesiTenggara.nolDuaQuickCount + sulawesiUtara.nolDuaQuickCount + sumateraBarat.nolDuaQuickCount + sumateraSelatan.nolDuaQuickCount + sumateraUtara.nolDuaQuickCount
totalSuaraQuickCountNasional = aceh.totalSuaraQuickCount + bali.totalSuaraQuickCount + banten.totalSuaraQuickCount + bengkulu.totalSuaraQuickCount + daerahIstimewaYogyakarta.totalSuaraQuickCount + daerahKhususIbukotaJakarta.totalSuaraQuickCount + gorontalo.totalSuaraQuickCount + jambi.totalSuaraQuickCount + jawaBarat.totalSuaraQuickCount + jawaTengah.totalSuaraQuickCount + jawaTimur.totalSuaraQuickCount + kalimantanBarat.totalSuaraQuickCount + kalimantanSelatan.totalSuaraQuickCount + kalimantanTengah.totalSuaraQuickCount + kalimantanTimur.totalSuaraQuickCount + kalimantanUtara.totalSuaraQuickCount + kepulauanBangka.totalSuaraQuickCount + kepulauanRiau.totalSuaraQuickCount + lampung.totalSuaraQuickCount + maluku.totalSuaraQuickCount + malukuUtara.totalSuaraQuickCount + nusaTenggaraBarat.totalSuaraQuickCount + nusaTenggaraTimur.totalSuaraQuickCount + papua.totalSuaraQuickCount + papuaBarat.totalSuaraQuickCount + riau.totalSuaraQuickCount + sulawesiBarat.totalSuaraQuickCount + sulawesiSelatan.totalSuaraQuickCount + sulawesiTengah.totalSuaraQuickCount + sulawesiTenggara.totalSuaraQuickCount + sulawesiUtara.totalSuaraQuickCount + sumateraBarat.totalSuaraQuickCount + sumateraSelatan.totalSuaraQuickCount + sumateraUtara.totalSuaraQuickCount

proporsiNolSatu = nolSatuQuickCountNasional/totalSuaraQuickCountNasional
proporsiNolDua = nolDuaQuickCountNasional/totalSuaraQuickCountNasional
persentaseNolSatu = proporsiNolSatu*100
persentaseNolDua = proporsiNolDua*100

print(proporsiNolSatu)
print(proporsiNolDua)

varianceNasional = aceh.getVariance(proporsiNolSatu) + bali.getVariance(proporsiNolSatu) + banten.getVariance(proporsiNolSatu) + bengkulu.getVariance(proporsiNolSatu) + daerahIstimewaYogyakarta.getVariance(proporsiNolSatu) + daerahKhususIbukotaJakarta.getVariance(proporsiNolSatu) + gorontalo.getVariance(proporsiNolSatu) + jambi.getVariance(proporsiNolSatu) + jawaBarat.getVariance(proporsiNolSatu) + jawaTengah.getVariance(proporsiNolSatu) + jawaTimur.getVariance(proporsiNolSatu) + kalimantanBarat.getVariance(proporsiNolSatu) + kalimantanSelatan.getVariance(proporsiNolSatu) + kalimantanTengah.getVariance(proporsiNolSatu) + kalimantanTimur.getVariance(proporsiNolSatu) + kalimantanUtara.getVariance(proporsiNolSatu) + kepulauanBangka.getVariance(proporsiNolSatu) + kepulauanRiau.getVariance(proporsiNolSatu) + lampung.getVariance(proporsiNolSatu) + maluku.getVariance(proporsiNolSatu) + malukuUtara.getVariance(proporsiNolSatu) + nusaTenggaraBarat.getVariance(proporsiNolSatu) + nusaTenggaraTimur.getVariance(proporsiNolSatu) + papua.getVariance(proporsiNolSatu) + papuaBarat.getVariance(proporsiNolSatu) + riau.getVariance(proporsiNolSatu) + sulawesiBarat.getVariance(proporsiNolSatu) + sulawesiSelatan.getVariance(proporsiNolSatu) + sulawesiTengah.getVariance(proporsiNolSatu) + sulawesiTenggara.getVariance(proporsiNolSatu) + sulawesiUtara.getVariance(proporsiNolSatu) + sumateraBarat.getVariance(proporsiNolSatu) + sumateraSelatan.getVariance(proporsiNolSatu) + sumateraUtara.getVariance(proporsiNolSatu)

standardErrorProporsi = np.sqrt(1/np.square(totalSuaraQuickCountNasional)*varianceNasional)
marginOfErrorProporsi = 2*standardErrorProporsi
marginOfErrorPercentage = marginOfErrorProporsi*100

sampleNolSatuNasional = aceh.sampleNolSatu + bali.sampleNolSatu + banten.sampleNolSatu + bengkulu.sampleNolSatu + daerahIstimewaYogyakarta.sampleNolSatu + daerahKhususIbukotaJakarta.sampleNolSatu + gorontalo.sampleNolSatu + jambi.sampleNolSatu + jawaBarat.sampleNolSatu + jawaTengah.sampleNolSatu + jawaTimur.sampleNolSatu + kalimantanBarat.sampleNolSatu + kalimantanSelatan.sampleNolSatu + kalimantanTengah.sampleNolSatu + kalimantanTimur.sampleNolSatu + kalimantanUtara.sampleNolSatu + kepulauanBangka.sampleNolSatu + kepulauanRiau.sampleNolSatu + lampung.sampleNolSatu + maluku.sampleNolSatu + malukuUtara.sampleNolSatu + nusaTenggaraBarat.sampleNolSatu + nusaTenggaraTimur.sampleNolSatu + papua.sampleNolSatu + papuaBarat.sampleNolSatu + riau.sampleNolSatu + sulawesiBarat.sampleNolSatu + sulawesiSelatan.sampleNolSatu + sulawesiTengah.sampleNolSatu + sulawesiTenggara.sampleNolSatu + sulawesiUtara.sampleNolSatu + sumateraBarat.sampleNolSatu + sumateraSelatan.sampleNolSatu + sumateraUtara.sampleNolSatu
sampleNolDuaNasional = aceh.sampleNolDua + bali.sampleNolDua + banten.sampleNolDua + bengkulu.sampleNolDua + daerahIstimewaYogyakarta.sampleNolDua + daerahKhususIbukotaJakarta.sampleNolDua + gorontalo.sampleNolDua + jambi.sampleNolDua + jawaBarat.sampleNolDua + jawaTengah.sampleNolDua + jawaTimur.sampleNolDua + kalimantanBarat.sampleNolDua + kalimantanSelatan.sampleNolDua + kalimantanTengah.sampleNolDua + kalimantanTimur.sampleNolDua + kalimantanUtara.sampleNolDua + kepulauanBangka.sampleNolDua + kepulauanRiau.sampleNolDua + lampung.sampleNolDua + maluku.sampleNolDua + malukuUtara.sampleNolDua + nusaTenggaraBarat.sampleNolDua + nusaTenggaraTimur.sampleNolDua + papua.sampleNolDua + papuaBarat.sampleNolDua + riau.sampleNolDua + sulawesiBarat.sampleNolDua + sulawesiSelatan.sampleNolDua + sulawesiTengah.sampleNolDua + sulawesiTenggara.sampleNolDua + sulawesiUtara.sampleNolDua + sumateraBarat.sampleNolDua + sumateraSelatan.sampleNolDua + sumateraUtara.sampleNolDua
sampleTotalSuaraNasional = aceh.sampleTotalSuara + bali.sampleTotalSuara + banten.sampleTotalSuara + bengkulu.sampleTotalSuara + daerahIstimewaYogyakarta.sampleTotalSuara + daerahKhususIbukotaJakarta.sampleTotalSuara + gorontalo.sampleTotalSuara + jambi.sampleTotalSuara + jawaBarat.sampleTotalSuara + jawaTengah.sampleTotalSuara + jawaTimur.sampleTotalSuara + kalimantanBarat.sampleTotalSuara + kalimantanSelatan.sampleTotalSuara + kalimantanTengah.sampleTotalSuara + kalimantanTimur.sampleTotalSuara + kalimantanUtara.sampleTotalSuara + kepulauanBangka.sampleTotalSuara + kepulauanRiau.sampleTotalSuara + lampung.sampleTotalSuara + maluku.sampleTotalSuara + malukuUtara.sampleTotalSuara + nusaTenggaraBarat.sampleTotalSuara + nusaTenggaraTimur.sampleTotalSuara + papua.sampleTotalSuara + papuaBarat.sampleTotalSuara + riau.sampleTotalSuara + sulawesiBarat.sampleTotalSuara + sulawesiSelatan.sampleTotalSuara + sulawesiTengah.sampleTotalSuara + sulawesiTenggara.sampleTotalSuara + sulawesiUtara.sampleTotalSuara + sumateraBarat.sampleTotalSuara + sumateraSelatan.sampleTotalSuara + sumateraUtara.sampleTotalSuara

persentaseSampleNolSatuNasional = np.divide(sampleNolSatuNasional, sampleTotalSuaraNasional)*100
persentaseSampleNolSatuNasionalSeries = pd.Series(persentaseSampleNolSatuNasional,
name = "Distribusi Persentase Suara 01 Stratified Random Sampling")

plotDistribusiNasionalStratifiedRandomSample, ax = plt.subplots(2,1)
sns.distplot(persentaseNolSatuNasional, kde=False, bins=100, ax = ax[0])
sns.distplot(persentaseSampleNolSatuNasionalSeries, kde=False, bins=100, ax = ax[1])
plotDistribusiNasionalStratifiedRandomSample.suptitle('{:.2f}% vs {:.2f}% Real Count Sementara \n {:.2f}% vs {:.2f}% Stratified Random Sampling dengan MOE = +/-{:.2f}%'.format(meanNolSatuNasional, meanNolDuaNasional, persentaseNolSatu, persentaseNolDua, marginOfErrorPercentage))

plotDistribusiNasionalStratifiedRandomSample.savefig("StratifiedRandomSample4.png")

 

Program Python ini juga menghasilkan plot distribusi yang membandingkan plot distribusi suara nasional seluruh TPS sementara dengan sampling yang dipakai. Selain itu pada grafik juga dibubuhkan hasil persentase quick count 01 maupun 02 dengan margin of error yang didapatkan dari rumus di atas Gambar 6 menunjukkan hasil stratified random sampling dengan margin of error dibawah 1% dengan jumlah sampel sebesar 2000.

StratifiedRandomSample4
Gambar 6. Stratified Random Sampling dari 2000 TPS dari seluruh TPS di Indonesia data sementara (456.135 TPS)

Seperti Random Sampling, saya juga menjalankan program ini 5 kali sehingga terdapat 5 hasil yang berbeda seperti dapat dilihat pada Gambar 7. Gambar 7 menunjukkan dari kelima data yang ditampilkan pada Gambar 6 maupun Gambar 7 memiliki Margin of Error pada sekitaran 0.95%, yakni di bawah 1%. Melihat hasil ini saya dapat memahami bahwa hasil Quick Count dengan 2000 TPS menggunakan metode Stratified Random Sampling itu bukan hal yang mustahil.

stratifiedRandomSamplingData
Gambar 7. Beberapa Hasil Stratified Random Sampling dengan Sampel 2000 TPS

Kesimpulan

Menggunakan metode Random Sampling, memang dibuktikan bahwa 2000 TPS tidak akan mencapai Margin of Error sebesar 1%, namun standard Quick Count menggunakan Stratified Random Sampling dan dengan metode ini, percobaan Penulis dalam melaksanakan simulasi Stratified Random Sampling dengan Python membuktikan bahwa Quick Count dari 2000 TPS dapat menghasilkan Margin of Error di bawah 1%

Margin of Error dari Random Sampling memerlukan data yang terdistribusi normal, sedangkan persamaan Margin of Error dari Stratified Random Sampling sudah dengan asumsi bahwa tidak secara persis terdistribusi normal (lihat Metodologi SMRC slide 9). Penggunaan persamaan Margin of Error dengan asumsi distribusi normal terhadap data persebaran suara yang tidak terdistribusi normal, menyebabkan adanya perbedaan pendapat jumlah sampel yang dibutuhkan.

Menggunakan metode Stratified Random Sampling maupun Random Sampling biasa seperti yang ditampilkan di artikel ini, tetap menghasilkan perbedaan suara 01 dan 02 yang cukup signifikan (> 2(moe)) sehingga kesimpulan dari Quick Count tetap sama, yakni pasangan 01 diprediksikan akan memenangi Pemilihan Presiden 2019.

 

EDITED 05 May: Menambahkan spoiler untuk menunjukkan code, sehingga bagi pembaca yang tidak ingin melihat code dapat melewati bagian code Python yang digunakan.

Bessel Function for FM Analysis

Artikel sebelumnya membahas FM sampai dengan persamaan yang umum digunakan untuk merepresentasikan FM jika sinyal baseband nya adalah sebuah sinyal sinusoidal. Sinyal FM memiliki hubungan yang nonlinear antara keluaran modulasi x_{FM}(t) dengan sinyal baseband m(t), sehingga sulit untuk menganalisis sinyal FM. Contohnya untuk menghitung bandwidth yang dibutuhkan oleh sinyal FM, digunakan sinyal sinusoidal dengan frekuensi tertinggi yang ada pada sinyal baseband.

Bessel Function and Identities

Fungsi Bessel adalah fungsi yang dipakai untuk menghitung side-band yang dihasilkan dari sebuah sinyal sinusoidal yang dimodulasi secara modulasi frekuensi pada sebuah sinyal pembawa. Fungsi Bessel adalah solusi y(x) dari persamaan diferensial Bessel:

\displaystyle x^2 \frac{d^2y}{dx^2}+x\frac{dy}{dx}+(x^2-\alpha^2)y = 0

Karena persamaan diferensial Bessel ini adalah persamaan differensial orde dua, maka terdapat dua solusi untuk persamaan tersebut, fungsi Bessel pertama (J_\alpha) dan fungsi Bessel kedua (Y_\alpha).

besselfunction
Gambar 1. Fungsi Bessel Pertama J_\alpha

Fungsi Bessel yang digunakan untuk FM adalah fungsi Bessel pertama (J_\alpha). Fungsi tersebut dapat dilihat pada Gambar 1, selain itu beberapa persamaan identitas dari fungsi Bessel adalah sebagai berikut:

\displaystyle \cos\big(z \sin(\theta)\big) = J_0(z) + 2\sum_{k=1}^{\infty}J_{2k}(z) \cos(2k\theta)

\displaystyle \sin\big(z \sin(\theta)\big) = 2\sum_{k=0}^{\infty} J_{2k+1}(z)sin\big((2k+1)\theta\big)

\displaystyle J_{-n}(z) = (-1)^nJ_n(z)

Bessel Function for FM Signal

Sinyal modulasi frekuensi dengan sinyal baseband sinusoidal dapat dituliskan dengan persamaan:

\displaystyle x_{FM}(t) = A_c \cos\big(2\pi f_ct+\beta \sin(2\pi f_mt)\big)

Untuk mendapatkan persamaan yang dapat direpresentasikan dengan fungsi Bessel, pertama gunakan identitas trigonometri untuk mendapatkan persamaan:

\displaystyle x_{FM}(t) = A_c \Big(\cos (2\pi f_ct)\cos\big(\beta \sin(2\pi f_mt)\big) - \sin(2\pi f_ct) \sin \big(\beta \sin(2\pi f_mt)\big)\Big)

Kita dapat mengambil bagian persamaan pertama: A_c\cos(2\pi f_ct)\cos(\beta \sin(2\pi f_mt)) dengan fungsi identitas Bessel menjadi:

\displaystyle A_c \cos(2\pi f_c t)\bigg(J_0(\beta) + 2\sum_{k=1}^{\infty} J_{2k}(z)\cos(2kf_mt)\bigg)

Menggunakan persamaan trigonometri 2\cos A\cos B = \cos (A-B) + \cos (A+B), didapatkan persamaan sebagai berikut:

\displaystyle A_c\Big(J_0(\beta)\cos(2\pi f_ct) + \sum_{k=1}^{\infty} J_{2k}(\beta)\big(\cos(2\pi(f_c - 2kf_m)t) + \cos(2\pi(f_c + 2kf_m)t)\big)\Big)

Mengetahui bahwa 2k adalah bilangan genap dan identitas Bessel J_{-n}(z) = (-1)^nJ_n(z) maka persamaan di atas dapat diubah menjadi:

\displaystyle A_c\sum_{n \in bilangan bulat genap} J_n(\beta)cos(2\pi (f_c+nf_m)t)

Kita dapat mengambil bagian kedua pada persamaan awal: A_c\sin(2\pi f_ct)\sin(\beta\sin(2\pi f_mt)) dengan fungsi identitas Bessel menjadi:

\displaystyle A_c\sin(2\pi f_ct)\bigg(2\sum_{k=0}^{\infty} J_{2k}(\beta)\sin((2k+1)f_mt)\bigg)

Dengan menggunakan persamaan trigonometri 2\sin A\sin B = \cos (A-B) - \cos(A+B), didapatkan persamaan sebagai berikut:

\displaystyle A_c\sum_{k=0}^{\infty} J_{2k+1}(\beta) \bigg(\cos(2\pi (f_c-(2k+1)f_m)t) - \cos(2\pi (f_c+(2k+1)f_m)t)\bigg)

Mengetahui bahwa 2k+1 adalah bilangan ganjil, maka identitas Bessel J_{-n}(z) = (-1)^nJ_n(z) dapat dipakai pada persamaan di atas untuk mendapatkan

\displaystyle -A_c\sum_{n \in bilangan bulat ganjil}J_n(\beta)\cos(2\pi(f_c+nf_m)t)

Menambahkan kedua bagian bilangan ganjil dan bilangan genap kita mendapatkan sinyal FM dengan fungsi Bessel sebagai berikut:

\displaystyle A_c \sum_{k=-\infty}^{\infty} J_k(\beta)\cos(2\pi (f_c+kf_m)t)

FM Signal in Frequency Domain

Jika kita melakukan transformasi Fourier pada fungsi di atas, maka kita akan mendapatkan fungsi:

\displaystyle \frac{A_c}{2} \sum_{k=-\infty}^{\infty} J_k(\beta)\Big(\delta (f-f_c-f_m)+\delta (f+f_c+f_m)\Big)

Screenshot from 2019-03-25 21.01.58
Gambar 2. Representasi sinyal FM pada domain frekuensi (Sumber: INFN)

Jumlah sideband dapat dihitung berdasarkan tabel Bessel pada Gambar 3, besaran sideband dapat dipakai untuk menghitung berapa besar bandwidth yang dibutuhkan oleh sebuah sinyal FM. Namun untuk mengaproksimasi bandwidth sinyal FM, persamaan Carson sering dipakai sebagai panduan mengukur bandwidth yang lebih praktis. Nilai bandwidth dapat diaproksimasi dengan persamaan:

\displaystyle BW_{FM} = 2(\beta + 1)f_m

Screenshot from 2019-03-25 21.21.48
Gambar 3. Tabel Bessel (Sumber: USNA)

Pendekatan bandwidth menggunakan persamaan Carson ini lebih praktis akibat sideband paling besar yang ada di tabel Bessel memiliki magnituda yang dapat diabaikan. Nilainya jauh lebih rendah daripada carrier, yakni di bawah -10dBc.

Pada artikel selanjutnya, pembahasan tentang FM akan berlanjut dengan demodulasi FM.

Installing GNU Radio and RTL-SDR on ElementaryOS

This is another GNU Radio and RTL2832U Software Defined Radio tutorial based on the article I did on Ubuntu 18.04. ElementaryOS is one of Ubuntu based operating system. The ElementaryOS is designed to be easy to use and the user interface is a lot like the macOS. This ElementaryOS GNU Radio installation tutorial is made for people who just started using Linux so they can transition painlessly to using Linux for RTL SDR and GNU Radio purposes.

Installing the GNU Radio and GNU Radio Companion

Installing GNU Radio and the GNU Radio companion is as easy on ElementaryOS as it is on Ubuntu 18.04, all we need is to access the gnuradio package from APT.

# apt install gnuradio

The package installation takes a while, after finishing the GNU Radio 3.7.11-10 is finally installed on the computer. You can access the GNU Radio Companion program via Terminal or via Application Launcher.

$ gnuradio-companion

Now, we can use GNU Radio and the GNU Radio Companion, the next step of this tutorial is to install the RTL SDR package to use with GNU Radio. This will allow us to receive the IQ data from RTL SDR and use the GNU Radio to process the data as we need.

Screenshot from 2019-03-20 20.36.12
Figure 1. GNU Radio Companion on ElementaryOS

GNU Radio Source

As we can see on the Terminal output of gnuradio installation, the rtl-sdr driver is already installed, the only thing needed to be installed is the GNU Radio Blocks for RTL-SDR via the gr-osmosdr package. The gr-osmosdr is already available in the APT in ElementaryOS.

# apt install gr-osmosdr

After installing the GNU OsmoSDR module, we can access the RTL-SDR source block on GNU Radio Companion under the Sources category.

Screenshot from 2019-03-20 20-59-14
Figure 2. RTL SDR Source on GNU Radio Companion

This concludes the tutorial on how to install GNU Radio Companion on ElementaryOS. Other Ubuntu based operating system should work the same, the APT can be used as a simple way to install the GNU Radio. However, it may not be the latest version, if you need the latest version always use the source and compile your own version by CMake.

Installing GNU Radio for Software Defined Radio on Ubuntu 18.04

As I mentioned previously on my Frequency Modulation Basics post, I would be taking a practical approach to explain the concept of Frequency Modulation. This tutorial explains how to install the GNU Radio on Ubuntu 18.04 alongside the GNU Radio Companion and the SDR dongle support.

Installing the GNU Radio and GNU Radio Companion

First, we can begin by installing the GNU radio package via APT, but check first if the version in the APT system is up to date. As of the writing of this article, GNU Radio on Ubuntu 18.04’s APT is on 3.7.11 and the current version is on 3.7.13.4. To simplify matters, I will use the Ubuntu 18.04’s APT version until I encounter a bug that will only be fixed on the latest version.

# apt-get install gnuradio

The package installation takes a while, after finishing the GNU Radio 3.7.11-10 is finally installed on the computer. You can access the GNU Radio Companion program via Terminal or via Application Launcher.

$ gnuradio-companion

Now, we can use GNU Radio and the GNU Radio Companion, the next step of this tutorial is to install the RTL SDR package to use with GNU Radio. This will allow us to receive the IQ data from RTL SDR and use the GNU Radio to process the data as we need.

Screenshot from 2019-03-20 09-20-40
Figure 1. GNU Radio Companion Window

Software Defined Radio GNU Radio Module

The Software Defined Radio that we will be using is the ubiquitous and inexpensive RTL2832U based DVB-T Dongles turned to Software Defined Radio. We can use the GNU Radio module developed by Osmocom. To build this GNU Radio block module, we need at least GNU Radio v3.7, which is already installed via the Ubuntu APT.

We can install the GNU Radio Osmocom SDR module from the git repository of osmocom or via the github mirror. Before installing the GNU Radio Source, we first must install the RTL-SDR driver created by Osmocom for RTL2832U-based Software Defined Radio dongles.

RTL2382U Driver

First, we have to install any dependencies and required packages before we can build our GNU Radio Osmocom SDR module. The required packages are cmake and build-essential to make sure we can compile the source and install the module. We also need the libusb-1.0-0-dev for the SDR.

# apt-get update
# apt-get install cmake build-essential libusb-1.0-0-dev

Then, we have to clone the repository, save the cloned repository in the desired place, for example we can put it on the Downloads folder

$ cd ~/Downloads
$ git clone https://github.com/osmocom/rtl-sdr.git

After cloning the repository, we can begin installing first by creating the build file and compiling the source via CMake. After compiling, we can install the rtl_sdr. Use the following commands on the Terminal:

$ mkdir build
$ cd build
$ cmake ../ -DINSTALL_UDEV_RULES=ON -DDETACH_KERNEL_DRIVER=ON
$ make
# make install
# ldconfig

Make sure to have both the build options INSTALL_UDEV_RULES=ON and DETACH_KERNEL_DRIVER=ON. UDEV_RULES is needed to access the dongle as non-root user, and DETACH_KERNEL_DRIVER will detach the default kernel driver for the RTL2832U DVB.

GNU Radio Source

After installing the RTL SDR driver, we need to install the GNU Radio Source module that is provided also by Osmocom. We can also install this automatically via APT-GET since we don’t need  to change any build configuration, use the following command to install the gr-osmosdr package:

# apt-get install gr-osmosdr

After installing, we can access the RTL SDR source on GNU Radio Companion menu under the Sources category.

Screenshot from 2019-03-20 10-58-43
Figure 2. RTL-SDR Source on GNU Radio Companion

That’s it for today’s tutorial, we will explore more about GNU Radio and RTL SDR when I have finished my Frequency Modulation basics series and we will demonstrate the analysis of a real narrow-band and wide-band FM wave on SDR + GNU Radio Companion.