Apa yang akan dipelajari hari ini

Di sesi ini kita akan mempelajari beberapa teknik statistik deskriptif dasar yang dapat kita gunakan untuk melakukan eksplorasi data. Di antaranya, kita akan mempelajari cara-cara menghitung cross-tab, indeks central tendency seperti mean, dan variasi di dalam data. Kita juga akan mempelajari sedikit tentang korelasi. Terakhir, kita akan membahas bagaimana menyimpan data tersebut di dalam tabel yang dapat dibaca dengan mudah.

Catatan: Sumber utama tutorial adalah buku Danielle Navarro: Learning Statistics with R (https://learningstatisticswithr.com/), halaman di Stats and R ini (https://statsandr.com/blog/descriptive-statistics-in-r), dan petunjuk dari Tidyverse (https://tidyverse.org)

0. Persiapkan R

Pertama kita nyalakan package terlebih dahulu dan set working directory

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages ------------------------------------------------------------ tidyverse 1.3.1 --
v ggplot2 3.3.5.9000     v purrr   0.3.4     
v tibble  3.1.6          v dplyr   1.0.7     
v tidyr   1.1.4          v stringr 1.4.0     
v readr   2.1.0          v forcats 0.5.1     
-- Conflicts --------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
setwd("D:/Temp/R Tutoring/Jan 21")

Lalu kita masukkan data

df <- read_csv("./Session 3/Jajan Q1.csv")
Rows: 25 Columns: 6
-- Column specification -----------------------------------------------------------------------------
Delimiter: ","
chr (3): nama, bulan, jajan
dbl (3): jumlah, harga, pengeluaran

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
df

Data untuk simulasi missing value

df_missing <- read_csv("./Session 3/Jajan Q1 - Sheet1 (missing).csv", )
Rows: 25 Columns: 6
-- Column specification -----------------------------------------------------------------------------
Delimiter: ","
chr (3): nama, bulan, jajan
dbl (3): jumlah, harga, pengeluaran

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
df_missing

1. Statistik deskriptif

Kita sudah mengenal sedikit tentang bagaimana memunculkan hitungan statistik deskriptif sebelumnya, dengan fungsy summary() Ingat bahwa kalau kita menggunakan summary() pada sebuah data numerik, kita akan mendapatkan deskripsi seperti berikut

summary(df$pengeluaran)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  30000   60000  110000  236600  180000 1200000 

Namun, terdapat cara-cara lebih spesifik untuk mendapatkan deskripsi tersebut, untuk menunjukkan central tendency, variasi, dan distribusi dalam data.

1.1. Central tendency dan variasi dalam data

1.1.1. Central tendency

Mean

Untuk mendapatkan mean, caranya cukup mudah. Gunakan fungsi mean().

mean(df$pengeluaran)
[1] 236600

Untuk kalkulasi seperti ini, jika ada missing value di dalam kolom yang dihitung, maka kita harus memberikan perintah apa yang dilakukan terhadap missing value tersebut. Jika tidak, maka akan keluar error dalam perhitungan. Gunakan na.rm = TRUE untuk meminta mean() mengabaikan row dengan missing value.Hal ini berlaku untuk kebanyakan fungsi kalkulasi dari base R.

# Lihat apa yang terjadi jika kita menggunakan kolom yang memiliki missing value
mean(df_missing$pengeluaran)
[1] NA
# Sekarang kita tambahkan na.rm = TRUE
mean(df_missing$pengeluaran, na.rm = TRUE)
[1] 245714.3

Median

Hitungan central tendency lain yang sering digunakan adalah median. Median dapat dihitung dengan median()

median(df$pengeluaran)
[1] 110000

1.1.2. Variasi data

Quantile

untuk menghitung kuartil data, kita bisa menggunakan fungsi quantile()

quantile(df$pengeluaran)
     0%     25%     50%     75%    100% 
  30000   60000  110000  180000 1200000 

Untuk hasil lebih spesifik, kita bisa menuliskan potongan data di kuartil keberapa yang kita inginkan.

quantile(df$pengeluaran, 0.2)
  20% 
60000 

Range dan nilai max-min

Fungsi range secara otomatis menunjukkan nilai paling rendah (min) dan paling tinggi (max) dari kolom.

range(df$pengeluaran)
[1]   30000 1200000

Namun, kita bisa menggunakan min atau max jika kita ingin mendapatkan salah satu nilai itu saja.

min(df$pengeluaran)
[1] 30000
max(df$pengeluaran)
[1] 1200000

Standard deviation

Standard deviation adalah salah satu cara paling umum untuk melihat variasi dari suatu data, yang merujuk ke seberapa jauh umumnya data point di dalam suatu set berada di luar mean. Di dalam R, kita bisa hitung hal ini dengan mudah menggunakan sd()

sd(df$pengeluaran)
[1] 321881.6

1.2. Crosstab

Untuk melakukan crosstab, kita memiliki dua pilihan, table() atau xtab(). Keduanya secara output tidak berbeda, walau ada sedikit perbedaan di dalam sintaks. Dua contoh di bawah menggunakan masing-masing fungsi menampilkan berapa kali setiap orang di dalam dataset Jajan melakukan pembelian di setiap bulan.

table(df$nama, df$bulan)
       
        Februari Januari Maret
  Alvin        4       4     3
  Bene         4       2     2
  Cakra        2       3     1
xtabs(~ df$nama + df$bulan)
       df$bulan
df$nama Februari Januari Maret
  Alvin        4       4     3
  Bene         4       2     2
  Cakra        2       3     1

Biasanya, kita lebih perlu untuk mengetahui proporsi (%) dari suatu cross tab, bukan jumlahnya secara riil. Untuk itu, kita bisa menggunakan fungsi prop.table().

prop.table(table(df$nama, df$bulan))
       
        Februari Januari Maret
  Alvin     0.16    0.16  0.12
  Bene      0.16    0.08  0.08
  Cakra     0.08    0.12  0.04

Fungsi prop.table() secara otomatis akan menghitung proporsi dari setiap sel dengan populasi. Namun, kita bisa memerintahkan untuk menghitung per row atau kolom.

# Proporsi per row 
prop.table(table(df$nama, df$bulan), 1)
       
         Februari   Januari     Maret
  Alvin 0.3636364 0.3636364 0.2727273
  Bene  0.5000000 0.2500000 0.2500000
  Cakra 0.3333333 0.5000000 0.1666667
# Proporsi per kolom
prop.table(table(df$nama, df$bulan), 2)
       
         Februari   Januari     Maret
  Alvin 0.4000000 0.4444444 0.5000000
  Bene  0.4000000 0.2222222 0.3333333
  Cakra 0.2000000 0.3333333 0.1666667

1.3. Melakukan kalkulasi terkelompok (grouped)

Seringkali kita perlu melakukan deskripsi berdasarkan kelompok atau kategori tertentu dalam data. Misalnya, kita ingin melihat secara rata-rata, Alvin, Bene dan Cakra menghabiskan berapa untuk jajan. Terdapat beberapa cara untuk melakukan ini.

aggregate() dan describeBy()

Cara yang cepat dan mudah adalah dengan aggregate() dan describeBy(). Cara-cara ini mirip dengan kalau kita menggunakan summary:

summary(df)
     nama              bulan              jajan               jumlah         harga        
 Length:25          Length:25          Length:25          Min.   :1.00   Min.   :  30000  
 Class :character   Class :character   Class :character   1st Qu.:1.00   1st Qu.:  30000  
 Mode  :character   Mode  :character   Mode  :character   Median :1.00   Median :  60000  
                                                          Mean   :2.04   Mean   : 200800  
                                                          3rd Qu.:2.00   3rd Qu.:  80000  
                                                          Max.   :8.00   Max.   :1200000  
  pengeluaran     
 Min.   :  30000  
 1st Qu.:  60000  
 Median : 110000  
 Mean   : 236600  
 3rd Qu.: 180000  
 Max.   :1200000  

Namun, kita bisa memerintahkan R untuk membagi hasilnya per kategori tertentu. Misalnya dengan describeBy, fungsi yang ada di dalam package psych.

library(psych)

Attaching package: ‘psych’

The following objects are masked from ‘package:ggplot2’:

    %+%, alpha
describeBy(x = df[c("pengeluaran", "jumlah")], group = df$nama)

 Descriptive statistics by group 
group: Alvin
--------------------------------------------------------------------------- 
group: Bene
--------------------------------------------------------------------------- 
group: Cakra

describeBy() memberikan summary data yang terkategorisasikan. Fungsi ini akan memberikan hasil yang serupa dengan summary, namun dengan tambahan adanya pengelompokkan data berdasarkan suatu variabel faktor.

Cara lainnya adalah dengan aggregate().

aggregate(formula = pengeluaran ~ nama, 
          data = df, 
          FUN = mean)
aggregate(formula = pengeluaran ~ nama, 
          data = df, 
          FUN = SD)

summarise() dan group_by()

Ada cara yang lebih fleksibel, walau sedikit lebih kompleks, yaitu dengan kombinasi summarise() dan group_by(). Secara prinsip, cara ini tidak jauh beda dengan aggregate(), dan hasilnya mirip dengan jika kita menggunakan aggregate() untuk beberapa fungsi sekaligus, dan langsung membuat sebuah tabel dari fungsi tersebut. Lihat contoh di bawah ini:

Perhatikan bahwa di sini digunakan fungsi %>% yang artinya untuk semua fungsi berikutnya akan merujuk ke dataset di awal, yang dikombinasikan dengan <-, untuk menyimpan sebagai objek tabel baru.

Fungsi groupBy() di sini memberikan perintah untuk mengelompokkan berdasarkan nama. Di fungsi ini kita juga bisa membuat tabulasi berdasarkan lebih dari satu variabel. Misalkan, kita ingin melihat mean dan standard deviation per orang per bulan:

# Clean up: membetulkan sort order dari faktor 'bulan'
df$bulan <- factor(df$bulan, levels=c("Januari", "Februari", "Maret"))
tab_desc <-df %>% 
    group_by(nama, bulan) %>% 
    summarise(mean  = mean(pengeluaran),
              median = median(pengeluaran), 
              sd = sd(pengeluaran),
              n = n())
`summarise()` has grouped output by 'nama'. You can override using the `.groups` argument.
tab_desc

summarise() membuat kalkulasi berdasrkan fungsi tertentu seperti mean atau standard deviation. Untuk melihat fungsi lengkapnya, gunakan ?summarise untuk membuka dokumentasi dari fungsi.

1.4. Beberapa deskripsi data lainnya

Histogram

Histogram sangat praktis untuk melihat distribusi data secara visual. Gunakan hist() untuk membuat histogram.

hist(df$pengeluaran)

Boxplot

Dengan boxplot, kita bisa langsung melihat potensi nilai-nilai outlier, atau lebih tepatnya, nilai yang jauh di atas interquartile range (IQR). Gunakan boxplot untuk menggambar figur ini.

boxplot(df$pengeluaran)

Scatterplot

Scatterplot sangat berguna untuk melihat distribusi nilai dari dua variabel, dan sebuah instrumen yang memudahkan eksplorasi hubungan antara dua variabel.plot adalah fungsi yang digunakan untuk membuat scatterplot sederhana.

plot(df$pengeluaran, df$jumlah)

Q-Q Plot

Q-Q Plot (quantile-quantile plot) adalah metode untuk melihat normalitas data. Figur ini bisa menjadi indikasi awal apakah suatu variabel normal atau tidak, di mana normalitas didefinisikan sebagai sebuah distribusi data yang bersifat unimodal.Gunakan qqnorm untuk menggambar Q-Q Plot.

qqnorm(df$pengeluaran)
qqline(df$pengeluaran)

2. Korelasi

Korelasi, walaupun pada dasarnya adalah sebuah teknik inferensial, berguna untuk mengeksplorasi potensi keterhubungan antara variabel-variabel. Hal ini terutama jika kita menggunakan sebuah tabel korelasi.

Fungsi untuk menghitung korelasi di dalam R adalah cor(). cor() dapat membuat tabel korelasi dengan lebih dari 2 variabel sekaligus. Tabel dari cor() akan memuat koefisien korelasi yang dihitung dari korelasi Spearman secara default.

cor(df[c("pengeluaran", "jumlah", "harga")])
            pengeluaran     jumlah      harga
pengeluaran   1.0000000 -0.1567716  0.9874424
jumlah       -0.1567716  1.0000000 -0.3076555
harga         0.9874424 -0.3076555  1.0000000

Kalau kita ingin melihat p-value dari korelasi, berbarengan dengan koefisiennya, kita bisa gunakan fungsi corr.test(), yang merupakan bagian dari package psych.

corr.test(df[c("pengeluaran", "jumlah", "harga")])
Call:corr.test(x = df[c("pengeluaran", "jumlah", "harga")])
Correlation matrix 
            pengeluaran jumlah harga
pengeluaran        1.00  -0.16  0.99
jumlah            -0.16   1.00 -0.31
harga              0.99  -0.31  1.00
Sample Size 
[1] 25
Probability values (Entries above the diagonal are adjusted for multiple tests.) 
            pengeluaran jumlah harga
pengeluaran        0.00   0.45  0.00
jumlah             0.45   0.00  0.27
harga              0.00   0.13  0.00

 To see confidence intervals of the correlations, print with the short=FALSE option

Opsi lainnya adalah melihat kalkulasi korelasi secara visual. cor.plot(), bagian dari psych, adalah fungsi yang dapat digunakan untuk melakukan visualisasi tabel korelasi.

cor.plot(df[c("pengeluaran", "jumlah", "harga")])

LS0tDQp0aXRsZTogIlNlc3Npb24gMyBFa3NwbG9yYXNpIERhdGEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIyBBcGEgeWFuZyBha2FuIGRpcGVsYWphcmkgaGFyaSBpbmlcDQpEaSBzZXNpIGluaSBraXRhIGFrYW4gbWVtcGVsYWphcmkgYmViZXJhcGEgdGVrbmlrIHN0YXRpc3RpayBkZXNrcmlwdGlmIGRhc2FyIHlhbmcgZGFwYXQga2l0YSBndW5ha2FuIHVudHVrIG1lbGFrdWthbiBla3NwbG9yYXNpIGRhdGEuIERpIGFudGFyYW55YSwga2l0YSBha2FuIG1lbXBlbGFqYXJpIGNhcmEtY2FyYSBtZW5naGl0dW5nIGNyb3NzLXRhYiwgaW5kZWtzIGNlbnRyYWwgdGVuZGVuY3kgc2VwZXJ0aSBtZWFuLCBkYW4gdmFyaWFzaSBkaSBkYWxhbSBkYXRhLiBLaXRhIGp1Z2EgYWthbiBtZW1wZWxhamFyaSBzZWRpa2l0IHRlbnRhbmcga29yZWxhc2kuIFRlcmFraGlyLCBraXRhIGFrYW4gbWVtYmFoYXMgYmFnYWltYW5hIG1lbnlpbXBhbiBkYXRhIHRlcnNlYnV0IGRpIGRhbGFtIHRhYmVsIHlhbmcgZGFwYXQgZGliYWNhIGRlbmdhbiBtdWRhaC4gDQoNCioqKkNhdGF0YW46ICBTdW1iZXIgdXRhbWEgdHV0b3JpYWwgYWRhbGFoICBidWt1IERhbmllbGxlIE5hdmFycm86IExlYXJuaW5nIFN0YXRpc3RpY3Mgd2l0aCBSIChodHRwczovL2xlYXJuaW5nc3RhdGlzdGljc3dpdGhyLmNvbS8pLCBoYWxhbWFuIGRpIFN0YXRzIGFuZCBSIGluaSAoaHR0cHM6Ly9zdGF0c2FuZHIuY29tL2Jsb2cvZGVzY3JpcHRpdmUtc3RhdGlzdGljcy1pbi1yKSwgZGFuIHBldHVuanVrIGRhcmkgVGlkeXZlcnNlIChodHRwczovL3RpZHl2ZXJzZS5vcmcpKioqDQoNCiMjIDAuIFBlcnNpYXBrYW4gUg0KUGVydGFtYSBraXRhIG55YWxha2FuIHBhY2thZ2UgdGVybGViaWggZGFodWx1IGRhbiBzZXQgd29ya2luZyBkaXJlY3RvcnkNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpzZXR3ZCgiRDovVGVtcC9SIFR1dG9yaW5nL0phbiAyMSIpDQpgYGANCg0KTGFsdSBraXRhIG1hc3Vra2FuIGRhdGEgDQpgYGB7cn0NCmRmIDwtIHJlYWRfY3N2KCIuL1Nlc3Npb24gMy9KYWphbiBRMS5jc3YiKQ0KZGYNCmBgYA0KRGF0YSB1bnR1ayBzaW11bGFzaSBtaXNzaW5nIHZhbHVlIA0KDQpgYGB7cn0NCmRmX21pc3NpbmcgPC0gcmVhZF9jc3YoIi4vU2Vzc2lvbiAzL0phamFuIFExIC0gU2hlZXQxIChtaXNzaW5nKS5jc3YiLCApDQpkZl9taXNzaW5nDQpgYGANCiMjIDEuIFN0YXRpc3RpayBkZXNrcmlwdGlmDQpLaXRhIHN1ZGFoIG1lbmdlbmFsIHNlZGlraXQgdGVudGFuZyBiYWdhaW1hbmEgbWVtdW5jdWxrYW4gaGl0dW5nYW4gc3RhdGlzdGlrIGRlc2tyaXB0aWYgc2ViZWx1bW55YSwgZGVuZ2FuIGZ1bmdzeSBgc3VtbWFyeSgpYCBJbmdhdCBiYWh3YSBrYWxhdSBraXRhIG1lbmdndW5ha2FuIGBzdW1tYXJ5KClgIHBhZGEgc2VidWFoIGRhdGEgbnVtZXJpaywga2l0YSBha2FuIG1lbmRhcGF0a2FuIGRlc2tyaXBzaSBzZXBlcnRpIGJlcmlrdXQNCmBgYHtyfQ0Kc3VtbWFyeShkZiRwZW5nZWx1YXJhbikNCmBgYA0KTmFtdW4sIHRlcmRhcGF0IGNhcmEtY2FyYSBsZWJpaCBzcGVzaWZpayB1bnR1ayBtZW5kYXBhdGthbiBkZXNrcmlwc2kgdGVyc2VidXQsIHVudHVrIG1lbnVuanVra2FuIGNlbnRyYWwgdGVuZGVuY3ksIHZhcmlhc2ksIGRhbiBkaXN0cmlidXNpIGRhbGFtIGRhdGEuDQoNCiMjIyAxLjEuIENlbnRyYWwgdGVuZGVuY3kgZGFuIHZhcmlhc2kgZGFsYW0gZGF0YQ0KDQojIyMjIDEuMS4xLiBDZW50cmFsIHRlbmRlbmN5DQoNCiMjIyMgTWVhbg0KDQpVbnR1ayBtZW5kYXBhdGthbiBtZWFuLCBjYXJhbnlhIGN1a3VwIG11ZGFoLiBHdW5ha2FuIGZ1bmdzaSBgbWVhbigpYC4NCg0KYGBge3J9DQptZWFuKGRmJHBlbmdlbHVhcmFuKQ0KYGBgDQpVbnR1ayBrYWxrdWxhc2kgc2VwZXJ0aSBpbmksIGppa2EgYWRhIG1pc3NpbmcgdmFsdWUgZGkgZGFsYW0ga29sb20geWFuZyBkaWhpdHVuZywgbWFrYSBraXRhIGhhcnVzIG1lbWJlcmlrYW4gcGVyaW50YWggYXBhIHlhbmcgZGlsYWt1a2FuIHRlcmhhZGFwIG1pc3NpbmcgdmFsdWUgdGVyc2VidXQuIEppa2EgdGlkYWssIG1ha2EgYWthbiBrZWx1YXIgZXJyb3IgZGFsYW0gcGVyaGl0dW5nYW4uIEd1bmFrYW4gYG5hLnJtID0gVFJVRWAgdW50dWsgbWVtaW50YSBgbWVhbigpYCBtZW5nYWJhaWthbiByb3cgZGVuZ2FuIG1pc3NpbmcgdmFsdWUuSGFsIGluaSBiZXJsYWt1IHVudHVrIGtlYmFueWFrYW4gZnVuZ3NpIGthbGt1bGFzaSBkYXJpIGJhc2UgUi4NCg0KYGBge3J9DQojIExpaGF0IGFwYSB5YW5nIHRlcmphZGkgamlrYSBraXRhIG1lbmdndW5ha2FuIGtvbG9tIHlhbmcgbWVtaWxpa2kgbWlzc2luZyB2YWx1ZQ0KbWVhbihkZl9taXNzaW5nJHBlbmdlbHVhcmFuKQ0KDQpgYGANCmBgYHtyfQ0KIyBTZWthcmFuZyBraXRhIHRhbWJhaGthbiBuYS5ybSA9IFRSVUUNCm1lYW4oZGZfbWlzc2luZyRwZW5nZWx1YXJhbiwgbmEucm0gPSBUUlVFKQ0KYGBgDQojIyMjIE1lZGlhbg0KSGl0dW5nYW4gY2VudHJhbCB0ZW5kZW5jeSBsYWluIHlhbmcgc2VyaW5nIGRpZ3VuYWthbiBhZGFsYWggbWVkaWFuLiBNZWRpYW4gZGFwYXQgZGloaXR1bmcgZGVuZ2FuIGBtZWRpYW4oKWANCmBgYHtyfQ0KbWVkaWFuKGRmJHBlbmdlbHVhcmFuKQ0KYGBgDQoNCiMjIyMgMS4xLjIuIFZhcmlhc2kgZGF0YQ0KDQojIyMjIFF1YW50aWxlDQp1bnR1ayBtZW5naGl0dW5nIGt1YXJ0aWwgZGF0YSwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGZ1bmdzaSBgcXVhbnRpbGUoKWAgIA0KYGBge3J9DQpxdWFudGlsZShkZiRwZW5nZWx1YXJhbikNCmBgYA0KVW50dWsgaGFzaWwgbGViaWggc3Blc2lmaWssIGtpdGEgYmlzYSBtZW51bGlza2FuIHBvdG9uZ2FuIGRhdGEgZGkga3VhcnRpbCBrZWJlcmFwYSB5YW5nIGtpdGEgaW5naW5rYW4uDQpgYGB7cn0NCnF1YW50aWxlKGRmJHBlbmdlbHVhcmFuLCAwLjIpDQpgYGANCg0KIyMjIyBSYW5nZSBkYW4gbmlsYWkgbWF4LW1pbg0KDQpGdW5nc2kgYHJhbmdlYCBzZWNhcmEgb3RvbWF0aXMgbWVudW5qdWtrYW4gbmlsYWkgcGFsaW5nIHJlbmRhaCAobWluKSBkYW4gcGFsaW5nIHRpbmdnaSAobWF4KSBkYXJpIGtvbG9tLg0KDQpgYGB7cn0NCnJhbmdlKGRmJHBlbmdlbHVhcmFuKQ0KYGBgDQpOYW11biwga2l0YSBiaXNhIG1lbmdndW5ha2FuIGBtaW5gIGF0YXUgYG1heGAgamlrYSBraXRhIGluZ2luIG1lbmRhcGF0a2FuIHNhbGFoIHNhdHUgbmlsYWkgaXR1IHNhamEuDQpgYGB7cn0NCm1pbihkZiRwZW5nZWx1YXJhbikNCm1heChkZiRwZW5nZWx1YXJhbikNCmBgYA0KDQojIyMjIFN0YW5kYXJkIGRldmlhdGlvbg0KU3RhbmRhcmQgZGV2aWF0aW9uIGFkYWxhaCBzYWxhaCBzYXR1IGNhcmEgcGFsaW5nIHVtdW0gdW50dWsgbWVsaWhhdCB2YXJpYXNpIGRhcmkgc3VhdHUgZGF0YSwgeWFuZyBtZXJ1anVrIGtlIHNlYmVyYXBhIGphdWggdW11bW55YSBkYXRhIHBvaW50IGRpIGRhbGFtIHN1YXR1IHNldCBiZXJhZGEgZGkgbHVhciBtZWFuLiBEaSBkYWxhbSBSLCBraXRhIGJpc2EgaGl0dW5nIGhhbCBpbmkgZGVuZ2FuIG11ZGFoIG1lbmdndW5ha2FuIGBzZCgpYA0KDQpgYGB7cn0NCnNkKGRmJHBlbmdlbHVhcmFuKQ0KYGBgDQoNCiMjIyAxLjIuIENyb3NzdGFiDQpVbnR1ayBtZWxha3VrYW4gY3Jvc3N0YWIsIGtpdGEgbWVtaWxpa2kgZHVhIHBpbGloYW4sIGB0YWJsZSgpYCBhdGF1IGB4dGFiKClgLiBLZWR1YW55YSBzZWNhcmEgb3V0cHV0IHRpZGFrIGJlcmJlZGEsIHdhbGF1IGFkYSBzZWRpa2l0IHBlcmJlZGFhbiBkaSBkYWxhbSBzaW50YWtzLiBEdWEgY29udG9oIGRpIGJhd2FoIG1lbmdndW5ha2FuIG1hc2luZy1tYXNpbmcgZnVuZ3NpIG1lbmFtcGlsa2FuIGJlcmFwYSBrYWxpIHNldGlhcCBvcmFuZyBkaSBkYWxhbSBkYXRhc2V0IEphamFuIG1lbGFrdWthbiBwZW1iZWxpYW4gZGkgc2V0aWFwIGJ1bGFuLg0KDQpgYGB7cn0NCnRhYmxlKGRmJG5hbWEsIGRmJGJ1bGFuKQ0KYGBgDQoNCg0KYGBge3J9DQp4dGFicyh+IGRmJG5hbWEgKyBkZiRidWxhbikNCmBgYA0KQmlhc2FueWEsIGtpdGEgbGViaWggcGVybHUgdW50dWsgbWVuZ2V0YWh1aSBwcm9wb3JzaSAoJSkgZGFyaSBzdWF0dSBjcm9zcyB0YWIsIGJ1a2FuIGp1bWxhaG55YSBzZWNhcmEgcmlpbC4gVW50dWsgaXR1LCBraXRhIGJpc2EgbWVuZ2d1bmFrYW4gZnVuZ3NpIGBwcm9wLnRhYmxlKClgLg0KYGBge3J9DQpwcm9wLnRhYmxlKHRhYmxlKGRmJG5hbWEsIGRmJGJ1bGFuKSkNCmBgYA0KRnVuZ3NpIGBwcm9wLnRhYmxlKClgIHNlY2FyYSBvdG9tYXRpcyBha2FuIG1lbmdoaXR1bmcgcHJvcG9yc2kgZGFyaSBzZXRpYXAgc2VsIGRlbmdhbiBwb3B1bGFzaS4gTmFtdW4sIGtpdGEgYmlzYSBtZW1lcmludGFoa2FuIHVudHVrIG1lbmdoaXR1bmcgcGVyIHJvdyBhdGF1IGtvbG9tLg0KDQpgYGB7cn0NCiMgUHJvcG9yc2kgcGVyIHJvdyANCnByb3AudGFibGUodGFibGUoZGYkbmFtYSwgZGYkYnVsYW4pLCAxKQ0KDQoNCiMgUHJvcG9yc2kgcGVyIGtvbG9tDQpwcm9wLnRhYmxlKHRhYmxlKGRmJG5hbWEsIGRmJGJ1bGFuKSwgMikNCg0KYGBgDQoNCiMjIyAxLjMuIE1lbGFrdWthbiBrYWxrdWxhc2kgdGVya2Vsb21wb2sgKGdyb3VwZWQpDQpTZXJpbmdrYWxpIGtpdGEgcGVybHUgbWVsYWt1a2FuIGRlc2tyaXBzaSBiZXJkYXNhcmthbiBrZWxvbXBvayBhdGF1IGthdGVnb3JpIHRlcnRlbnR1IGRhbGFtIGRhdGEuIE1pc2FsbnlhLCBraXRhIGluZ2luIG1lbGloYXQgc2VjYXJhIHJhdGEtcmF0YSwgQWx2aW4sIEJlbmUgZGFuIENha3JhIG1lbmdoYWJpc2thbiBiZXJhcGEgdW50dWsgamFqYW4uIFRlcmRhcGF0IGJlYmVyYXBhIGNhcmEgdW50dWsgbWVsYWt1a2FuIGluaS4NCg0KIyMjIyBhZ2dyZWdhdGUoKSBkYW4gZGVzY3JpYmVCeSgpDQpDYXJhIHlhbmcgY2VwYXQgZGFuIG11ZGFoIGFkYWxhaCBkZW5nYW4gYGFnZ3JlZ2F0ZSgpYCBkYW4gYGRlc2NyaWJlQnkoKWAuIENhcmEtY2FyYSBpbmkgbWlyaXAgZGVuZ2FuIGthbGF1IGtpdGEgbWVuZ2d1bmFrYW4gc3VtbWFyeToNCmBgYHtyfQ0Kc3VtbWFyeShkZikNCmBgYA0KTmFtdW4sIGtpdGEgYmlzYSBtZW1lcmludGFoa2FuIFIgdW50dWsgbWVtYmFnaSBoYXNpbG55YSBwZXIga2F0ZWdvcmkgdGVydGVudHUuIE1pc2FsbnlhIGRlbmdhbiBgZGVzY3JpYmVCeWAsIGZ1bmdzaSB5YW5nIGFkYSBkaSBkYWxhbSBwYWNrYWdlIGBwc3ljaGAuIA0KYGBge3J9DQppbnN0YWxsLnBhY2thZ2VzKCJwc3ljaCIpDQpsaWJyYXJ5KHBzeWNoKQ0KYGBgDQoNCmBgYHtyfQ0KZGVzY3JpYmVCeSh4ID0gZGZbYygicGVuZ2VsdWFyYW4iLCAianVtbGFoIildLCBncm91cCA9IGRmJG5hbWEpDQpgYGANCg0KYGRlc2NyaWJlQnkoKWAgbWVtYmVyaWthbiBzdW1tYXJ5IGRhdGEgeWFuZyB0ZXJrYXRlZ29yaXNhc2lrYW4uIEZ1bmdzaSBpbmkgYWthbiBtZW1iZXJpa2FuIGhhc2lsIHlhbmcgc2VydXBhIGRlbmdhbiBgc3VtbWFyeWAsIG5hbXVuIGRlbmdhbiB0YW1iYWhhbiBhZGFueWEgcGVuZ2Vsb21wb2trYW4gZGF0YSBiZXJkYXNhcmthbiBzdWF0dSB2YXJpYWJlbCBmYWt0b3IuDQoNCg0KQ2FyYSBsYWlubnlhIGFkYWxhaCBkZW5nYW4gYGFnZ3JlZ2F0ZSgpYC4gDQoNCmBgYHtyfQ0KIyBNZW5naGl0dW5nIG1lYW4NCmFnZ3JlZ2F0ZShmb3JtdWxhID0gcGVuZ2VsdWFyYW4gfiBuYW1hLCANCiAgICAgICAgICBkYXRhID0gZGYsIA0KICAgICAgICAgIEZVTiA9IG1lYW4pDQpgYGANCg0KYGBge3J9DQojIG1lbmdoaXR1bmcgc3RhbmRhcmQgZGV2aWF0aW9uDQphZ2dyZWdhdGUoZm9ybXVsYSA9IHBlbmdlbHVhcmFuIH4gbmFtYSwgDQogICAgICAgICAgZGF0YSA9IGRmLCANCiAgICAgICAgICBGVU4gPSBTRCkNCmBgYA0KDQojIyMjIHN1bW1hcmlzZSgpIGRhbiBncm91cF9ieSgpDQpBZGEgY2FyYSB5YW5nIGxlYmloIGZsZWtzaWJlbCwgd2FsYXUgc2VkaWtpdCBsZWJpaCBrb21wbGVrcywgeWFpdHUgZGVuZ2FuIGtvbWJpbmFzaSBgc3VtbWFyaXNlKClgIGRhbiBgZ3JvdXBfYnkoKWAuIFNlY2FyYSBwcmluc2lwLCBjYXJhIGluaSB0aWRhayBqYXVoIGJlZGEgZGVuZ2FuIGBhZ2dyZWdhdGUoKWAsIGRhbiBoYXNpbG55YSBtaXJpcCBkZW5nYW4gamlrYSBraXRhIG1lbmdndW5ha2FuIGBhZ2dyZWdhdGUoKWAgdW50dWsgYmViZXJhcGEgZnVuZ3NpIHNla2FsaWd1cywgZGFuIGxhbmdzdW5nIG1lbWJ1YXQgc2VidWFoIHRhYmVsIGRhcmkgZnVuZ3NpIHRlcnNlYnV0LiBMaWhhdCBjb250b2ggZGkgYmF3YWggaW5pOg0KYGBge3J9DQp0YWJfZGVzYyA8LWRmICU+JSANCiAgICBncm91cF9ieShuYW1hKSAlPiUgDQogICAgc3VtbWFyaXNlKG1lYW4gID0gbWVhbihwZW5nZWx1YXJhbiksIA0KICAgICAgICAgICAgICBtZWRpYW4gPSBtZWRpYW4ocGVuZ2VsdWFyYW4pLCANCiAgICAgICAgICAgICAgc2QgPSBzZChwZW5nZWx1YXJhbiksIA0KICAgICAgICAgICAgICBuID0gbigpKQ0KdGFiX2Rlc2MNCmBgYA0KUGVyaGF0aWthbiBiYWh3YSBkaSBzaW5pIGRpZ3VuYWthbiBmdW5nc2kgYCU+JWAgeWFuZyBhcnRpbnlhIHVudHVrIHNlbXVhIGZ1bmdzaSBiZXJpa3V0bnlhIGFrYW4gbWVydWp1ayBrZSBkYXRhc2V0IGRpIGF3YWwsIHlhbmcgZGlrb21iaW5hc2lrYW4gZGVuZ2FuIGA8LWAsIHVudHVrIG1lbnlpbXBhbiBzZWJhZ2FpIG9iamVrIHRhYmVsIGJhcnUuIA0KDQpGdW5nc2kgYGdyb3VwQnkoKWAgZGkgc2luaSBtZW1iZXJpa2FuIHBlcmludGFoIHVudHVrIG1lbmdlbG9tcG9ra2FuIGJlcmRhc2Fya2FuIG5hbWEuIERpIGZ1bmdzaSBpbmkga2l0YSBqdWdhIGJpc2EgbWVtYnVhdCB0YWJ1bGFzaSBiZXJkYXNhcmthbiBsZWJpaCBkYXJpIHNhdHUgdmFyaWFiZWwuIE1pc2Fsa2FuLCBraXRhIGluZ2luIG1lbGloYXQgbWVhbiBkYW4gc3RhbmRhcmQgZGV2aWF0aW9uIHBlciBvcmFuZyBwZXIgYnVsYW46DQpgYGB7cn0NCiMgQ2xlYW4gdXA6IG1lbWJldHVsa2FuIHNvcnQgb3JkZXIgZGFyaSBmYWt0b3IgJ2J1bGFuJw0KZGYkYnVsYW4gPC0gZmFjdG9yKGRmJGJ1bGFuLCBsZXZlbHM9YygiSmFudWFyaSIsICJGZWJydWFyaSIsICJNYXJldCIpKQ0KYGBgDQoNCmBgYHtyfQ0KdGFiX2Rlc2MgPC1kZiAlPiUgDQogICAgZ3JvdXBfYnkobmFtYSwgYnVsYW4pICU+JSANCiAgICBzdW1tYXJpc2UobWVhbiAgPSBtZWFuKHBlbmdlbHVhcmFuKSwNCiAgICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKHBlbmdlbHVhcmFuKSwgDQogICAgICAgICAgICAgIHNkID0gc2QocGVuZ2VsdWFyYW4pLA0KICAgICAgICAgICAgICBuID0gbigpKQ0KdGFiX2Rlc2MNCmBgYA0KDQpgc3VtbWFyaXNlKClgIG1lbWJ1YXQga2Fsa3VsYXNpIGJlcmRhc3JrYW4gZnVuZ3NpIHRlcnRlbnR1IHNlcGVydGkgbWVhbiBhdGF1IHN0YW5kYXJkIGRldmlhdGlvbi4gVW50dWsgbWVsaWhhdCBmdW5nc2kgbGVuZ2thcG55YSwgZ3VuYWthbiBgP3N1bW1hcmlzZWAgdW50dWsgbWVtYnVrYSBkb2t1bWVudGFzaSBkYXJpIGZ1bmdzaS4NCg0KDQojIyMgMS40LiBCZWJlcmFwYSBkZXNrcmlwc2kgZGF0YSBsYWlubnlhDQoNCg0KIyMjIyBIaXN0b2dyYW0NCkhpc3RvZ3JhbSBzYW5nYXQgcHJha3RpcyB1bnR1ayBtZWxpaGF0IGRpc3RyaWJ1c2kgZGF0YSBzZWNhcmEgdmlzdWFsLiBHdW5ha2FuIGBoaXN0KClgIHVudHVrIG1lbWJ1YXQgaGlzdG9ncmFtLg0KYGBge3J9DQpoaXN0KGRmJHBlbmdlbHVhcmFuKQ0KYGBgDQoNCiMjIyMgQm94cGxvdA0KRGVuZ2FuIGJveHBsb3QsIGtpdGEgYmlzYSBsYW5nc3VuZyBtZWxpaGF0IHBvdGVuc2kgbmlsYWktbmlsYWkgb3V0bGllciwgYXRhdSBsZWJpaCB0ZXBhdG55YSwgbmlsYWkgeWFuZyBqYXVoIGRpIGF0YXMgaW50ZXJxdWFydGlsZSByYW5nZSAoSVFSKS4gR3VuYWthbiBgYm94cGxvdGAgdW50dWsgbWVuZ2dhbWJhciBmaWd1ciBpbmkuDQpgYGB7cn0NCmJveHBsb3QoZGYkcGVuZ2VsdWFyYW4pDQpgYGANCg0KIyMjIyBTY2F0dGVycGxvdA0KU2NhdHRlcnBsb3Qgc2FuZ2F0IGJlcmd1bmEgdW50dWsgbWVsaWhhdCBkaXN0cmlidXNpIG5pbGFpIGRhcmkgZHVhIHZhcmlhYmVsLCBkYW4gc2VidWFoIGluc3RydW1lbiB5YW5nIG1lbXVkYWhrYW4gZWtzcGxvcmFzaSBodWJ1bmdhbiBhbnRhcmEgZHVhIHZhcmlhYmVsLmBwbG90YCBhZGFsYWggZnVuZ3NpIHlhbmcgZGlndW5ha2FuIHVudHVrIG1lbWJ1YXQgc2NhdHRlcnBsb3Qgc2VkZXJoYW5hLg0KYGBge3J9DQpwbG90KGRmJHBlbmdlbHVhcmFuLCBkZiRqdW1sYWgpDQpgYGANCg0KDQojIyMjIFEtUSBQbG90DQpRLVEgUGxvdCAocXVhbnRpbGUtcXVhbnRpbGUgcGxvdCkgYWRhbGFoIG1ldG9kZSB1bnR1ayBtZWxpaGF0IG5vcm1hbGl0YXMgZGF0YS4gRmlndXIgaW5pIGJpc2EgbWVuamFkaSBpbmRpa2FzaSBhd2FsIGFwYWthaCBzdWF0dSB2YXJpYWJlbCBub3JtYWwgYXRhdSB0aWRhaywgZGkgbWFuYSBub3JtYWxpdGFzIGRpZGVmaW5pc2lrYW4gc2ViYWdhaSBzZWJ1YWggZGlzdHJpYnVzaSBkYXRhIHlhbmcgYmVyc2lmYXQgdW5pbW9kYWwuR3VuYWthbiBgcXFub3JtYCB1bnR1ayBtZW5nZ2FtYmFyIFEtUSBQbG90Lg0KYGBge3J9DQpxcW5vcm0oZGYkcGVuZ2VsdWFyYW4pDQpxcWxpbmUoZGYkcGVuZ2VsdWFyYW4pDQpgYGANCg0KDQojIyMgMi4gS29yZWxhc2kNCktvcmVsYXNpLCB3YWxhdXB1biBwYWRhIGRhc2FybnlhIGFkYWxhaCBzZWJ1YWggdGVrbmlrIGluZmVyZW5zaWFsLCBiZXJndW5hIHVudHVrIG1lbmdla3NwbG9yYXNpIHBvdGVuc2kga2V0ZXJodWJ1bmdhbiBhbnRhcmEgdmFyaWFiZWwtdmFyaWFiZWwuIEhhbCBpbmkgdGVydXRhbWEgamlrYSBraXRhIG1lbmdndW5ha2FuIHNlYnVhaCB0YWJlbCBrb3JlbGFzaS4NCg0KRnVuZ3NpIHVudHVrIG1lbmdoaXR1bmcga29yZWxhc2kgZGkgZGFsYW0gUiBhZGFsYWggYGNvcigpYC4gYGNvcigpYCBkYXBhdCBtZW1idWF0IHRhYmVsIGtvcmVsYXNpIGRlbmdhbiBsZWJpaCBkYXJpIDIgdmFyaWFiZWwgc2VrYWxpZ3VzLiBUYWJlbCBkYXJpIGBjb3IoKWAgYWthbiBtZW11YXQga29lZmlzaWVuIGtvcmVsYXNpIHlhbmcgZGloaXR1bmcgZGFyaSBrb3JlbGFzaSBTcGVhcm1hbiBzZWNhcmEgZGVmYXVsdC4NCmBgYHtyfQ0KY29yKGRmW2MoInBlbmdlbHVhcmFuIiwgImp1bWxhaCIsICJoYXJnYSIpXSkNCmBgYA0KS2FsYXUga2l0YSBpbmdpbiBtZWxpaGF0IHAtdmFsdWUgZGFyaSBrb3JlbGFzaSwgYmVyYmFyZW5nYW4gZGVuZ2FuIGtvZWZpc2llbm55YSwga2l0YSBiaXNhIGd1bmFrYW4gZnVuZ3NpIGBjb3JyLnRlc3QoKWAsIHlhbmcgbWVydXBha2FuIGJhZ2lhbiBkYXJpIHBhY2thZ2UgYHBzeWNoYC4NCmBgYHtyfQ0KY29yci50ZXN0KGRmW2MoInBlbmdlbHVhcmFuIiwgImp1bWxhaCIsICJoYXJnYSIpXSkNCmBgYA0KT3BzaSBsYWlubnlhIGFkYWxhaCBtZWxpaGF0IGthbGt1bGFzaSBrb3JlbGFzaSBzZWNhcmEgdmlzdWFsLiBgY29yLnBsb3QoKWAsIGJhZ2lhbiBkYXJpIGBwc3ljaGAsIGFkYWxhaCBmdW5nc2kgeWFuZyBkYXBhdCBkaWd1bmFrYW4gdW50dWsgbWVsYWt1a2FuIHZpc3VhbGlzYXNpIHRhYmVsIGtvcmVsYXNpLg0KYGBge3J9DQpjb3IucGxvdChkZltjKCJwZW5nZWx1YXJhbiIsICJqdW1sYWgiLCAiaGFyZ2EiKV0pDQpgYGANCg0K