Identitas Praktikum

Komponen Keterangan
Mata kuliah Data Mining
Pertemuan 03
Topik Data Preprocessing Menggunakan Dataset Pemain Sepak Bola
Perangkat lunak R dan RStudio
Alokasi waktu 3 SKS / 150 menit
Acuan utama Han, Kamber, dan Pei (2012), Bab 3

Dataset praktikum ini menggunakan nama dan profil pemain sepak bola nyata. Namun, beberapa nilai pada dataset mentah sengaja dibuat bermasalah untuk keperluan pembelajaran preprocessing, misalnya missing value, duplikasi, kategori yang tidak konsisten, nilai ekstrem, dan kesalahan format.

Capaian Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:

  1. memahami struktur dan kualitas awal dataset pemain sepak bola;
  2. mengidentifikasi missing value, duplikasi, inkonsistensi kategori, dan nilai ekstrem;
  3. membersihkan data kategorik seperti posisi dan kewarganegaraan;
  4. menangani missing value menggunakan strategi yang sesuai;
  5. mendeteksi dan menangani outlier pada variabel numerik;
  6. melakukan transformasi data dengan Min-Max, Z-Score, dan Decimal Scaling;
  7. mengintegrasikan data profil pemain dengan data statistik tambahan;
  8. melakukan audit dataset setelah preprocessing; dan
  9. menyimpan dataset final serta log perubahan secara sistematis.

Skenario Praktikum

Sebuah analisis sederhana akan dilakukan terhadap 18 pemain sepak bola terkenal dari berbagai negara dan klub. Dataset memuat nama pemain, kewarganegaraan, posisi, usia, jumlah penampilan, gol, assist, dan estimasi nilai pasar.

Data dasar menggunakan pemain nyata seperti Lionel Messi, Cristiano Ronaldo, Kylian Mbappé, Lamine Yamal, Erling Haaland, Vinícius Júnior, Jude Bellingham, Mohamed Salah, Harry Kane, dan pemain lainnya. Statistik musim dijadikan konteks latihan, bukan sebagai dataset resmi untuk pemeringkatan pemain.

Beberapa masalah kualitas data sengaja dimasukkan ke dalam dataset mentah: missing value, nama negara/posisi yang tidak konsisten, spasi berlebih, duplikasi pemain, nilai usia atau nilai pasar yang ekstrem, serta beberapa identifier yang tidak cocok ketika data diintegrasikan.

Catatan metodologis: masalah kualitas data di bawah ini adalah sengaja ditambahkan untuk simulasi preprocessing. Jadi, jika ditemukan misalnya nilai pasar yang sangat besar atau data yang kosong, hal tersebut tidak boleh dianggap sebagai fakta asli tentang pemain tersebut.

Sumber dan Batasan Data

Nama pemain dan sebagian informasi profil/statistik diambil dari sumber publik sepak bola, terutama Transfermarkt. Sebagai contoh, halaman statistik Transfermarkt mencatat Cristiano Ronaldo dengan total 41 penampilan dan 35 gol pada 24/25, Kylian Mbappé dengan 59 penampilan dan 44 gol pada 24/25, serta Lamine Yamal dengan 55 penampilan dan 18 gol pada 24/25. Data Lionel Messi juga tersedia pada halaman statistik Transfermarkt, tetapi konteks kompetisinya berbeda karena ia bermain di MLS.

Penting: dataset praktikum ini bukan salinan mentah dari satu sumber online. Nilai dasar digunakan sebagai konteks nyata, lalu beberapa nilai sengaja diubah atau dikosongkan agar dataset memiliki masalah kualitas data yang dapat diproses. Karena itu, dataset ini cocok untuk pembelajaran preprocessing, bukan untuk membuat peringkat resmi pemain.

Sumber utama:

  • Transfermarkt — statistik Cristiano Ronaldo 24/25.
  • Transfermarkt — statistik Kylian Mbappé 24/25.
  • Transfermarkt — statistik Lamine Yamal 24/25.
  • Transfermarkt — statistik Erling Haaland.
  • Transfermarkt — statistik Lionel Messi.

Persiapan RStudio

Membuat Proyek

  1. Buka RStudio.
  2. Pilih File → New Project → New Directory → New Project.
  3. Beri nama proyek, misalnya praktikum_preprocessing_sepakbola.
  4. Simpan file .Rmd di dalam folder proyek.
  5. Klik Knit untuk menghasilkan HTML.

Memeriksa Lingkungan Kerja

getwd()
## [1] "C:/Users/user/Documents/SEM 5/DATA MINING"
R.version.string
## [1] "R version 4.5.0 (2025-04-11 ucrt)"

Membangun Dataset Praktikum

Dataset utama menggunakan 18 pemain nyata. Delapan variabel yang digunakan adalah:

  • player_id — identifier pemain;
  • nama — nama pemain;
  • negara — kewarganegaraan;
  • posisi — posisi utama;
  • usia — usia pemain;
  • penampilan_24_25 — jumlah penampilan pada konteks musim 2024/25;
  • gol_24_25 — jumlah gol;
  • assist_24_25 — jumlah assist;
  • nilai_pasar_juta — estimasi nilai pasar dalam juta euro.

Nilai statistik dasar dirangkum dari profil/statistik pemain yang tersedia secara publik, kemudian beberapa gangguan kualitas data ditambahkan secara sengaja agar dapat digunakan untuk latihan preprocessing.

Data Pemain

pemain_raw <- data.frame(
  player_id = c(
    "P001", "P002", "P003", "P004", "P005", "P006",
    "P007", "P008", "P009", "P010", "P011", "P012",
    "P013", "P014", "P015", "P016", "P017", "P017"
  ),
  nama = c(
    "Lionel Messi", "Cristiano Ronaldo", "Kylian Mbappé",
    "Lamine Yamal", "Erling Haaland", "Vinícius Júnior",
    "Jude Bellingham", "Mohamed Salah", "Harry Kane",
    "Bukayo Saka", "Kevin De Bruyne", "Rodri",
    "Robert Lewandowski", "Pedri", "Son Heung-min",
    "Cole Palmer", "Lautaro Martínez", "Lautaro Martínez"
  ),
  negara = c(
    "Argentina", "Portugal", "France", "Spain", "Norway", "Brazil",
    "England", "Egypt", "England", " England", "Belgium", "Spain",
    "Poland", "Spain", "South Korea", "England", NA, "Argentina"
  ),
  posisi = c(
    "Forward", "forward", "Forward", "RW", "ST", "LW",
    "Attacking Midfield", "RW", "ST", "RW", "CM", "DM",
    "ST", "CM", "LW", "AM", "ST", "ST"
  ),
  usia = c(
    37, 40, 26, 17, 24, 24, 21, 32, 31, 23, 33, 28,
    36, 22, 33, NA, 27, 27
  ),
  penampilan_24_25 = c(
    25, 41, 59, 55, 46, 58, 58, 52, 58, 37, 40, 35,
    52, 44, 46, 43, 57, 57
  ),
  gol_24_25 = c(
    21, 35, 44, 18, 31, 21, 15, 34, 38, 12, 6, 4,
    42, 6, 11, 18, 21, 21
  ),
  assist_24_25 = c(
    11, 4, 5, 25, 4, 18, 15, 23, 13, 10, 8, 7,
    3, 7, 12, 14, 8, 8
  ),
  nilai_pasar_juta = c(
    25, 12, 180, 200, 180, 200, 180, 9999, 75, 150,
    20, 130, 15, 140, 35, 120, 95, 95
  ),
  stringsAsFactors = FALSE
)

pemain_raw

Data Statistik Tambahan

Sumber kedua dibuat sebagai tabel ringkas statistik klub. Identifier sengaja menggunakan nama yang sedikit berbeda agar proses penyelarasan identifier dapat dipraktikkan.

statistik_raw <- data.frame(
  id_pemain = c(
    "P001", "P002", "P003", "P004", "P005", "P006",
    "P007", "P008", "P009", "P010", "P011", "P012",
    "P013", "P014", "P015", "P016", "P018"
  ),
  menit_bermain = c(
    2100, 3548, 4745, 4553, 3650, 4300, 4794, 4300,
    3900, 2800, 2500, 2900, 4000, 3200, 3600, 3400, 3700
  ),
  rating = c(
    8.1, 7.6, 8.0, 8.2, 7.8, 7.9, 7.7, 8.1, 7.9,
    7.5, 7.2, 7.4, 7.8, 7.6, 7.5, 8.0, 7.7
  ),
  stringsAsFactors = FALSE
)

statistik_raw

Konsep Data Preprocessing

Memahami Struktur Data

Dimensi Data

dim(pemain_raw)
## [1] 18  9

Nama Variabel

names(pemain_raw)
## [1] "player_id"        "nama"             "negara"           "posisi"          
## [5] "usia"             "penampilan_24_25" "gol_24_25"        "assist_24_25"    
## [9] "nilai_pasar_juta"

Struktur Data

str(pemain_raw)
## 'data.frame':    18 obs. of  9 variables:
##  $ player_id       : chr  "P001" "P002" "P003" "P004" ...
##  $ nama            : chr  "Lionel Messi" "Cristiano Ronaldo" "Kylian Mbappé" "Lamine Yamal" ...
##  $ negara          : chr  "Argentina" "Portugal" "France" "Spain" ...
##  $ posisi          : chr  "Forward" "forward" "Forward" "RW" ...
##  $ usia            : num  37 40 26 17 24 24 21 32 31 23 ...
##  $ penampilan_24_25: num  25 41 59 55 46 58 58 52 58 37 ...
##  $ gol_24_25       : num  21 35 44 18 31 21 15 34 38 12 ...
##  $ assist_24_25    : num  11 4 5 25 4 18 15 23 13 10 ...
##  $ nilai_pasar_juta: num  25 12 180 200 180 ...

Lima Baris Pertama

head(pemain_raw, 5)

Ringkasan Statistik

summary(pemain_raw)
##   player_id             nama              negara             posisi         
##  Length:18          Length:18          Length:18          Length:18         
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##       usia       penampilan_24_25   gol_24_25      assist_24_25  
##  Min.   :17.00   Min.   :25.00    Min.   : 4.00   Min.   : 3.00  
##  1st Qu.:24.00   1st Qu.:41.50    1st Qu.:12.75   1st Qu.: 7.00  
##  Median :27.00   Median :49.00    Median :21.00   Median : 9.00  
##  Mean   :28.29   Mean   :47.94    Mean   :22.11   Mean   :10.83  
##  3rd Qu.:33.00   3rd Qu.:57.00    3rd Qu.:33.25   3rd Qu.:13.75  
##  Max.   :40.00   Max.   :59.00    Max.   :44.00   Max.   :25.00  
##  NA's   :1                                                       
##  nilai_pasar_juta
##  Min.   :  12.0  
##  1st Qu.:  45.0  
##  Median : 125.0  
##  Mean   : 658.4  
##  3rd Qu.: 180.0  
##  Max.   :9999.0  
## 

Mengukur Kualitas Awal

Missing Value

colSums(is.na(pemain_raw))
##        player_id             nama           negara           posisi 
##                0                0                1                0 
##             usia penampilan_24_25        gol_24_25     assist_24_25 
##                1                0                0                0 
## nilai_pasar_juta 
##                0

Persentase Missing Value

round(
  colMeans(is.na(pemain_raw)) * 100,
  2
)
##        player_id             nama           negara           posisi 
##             0.00             0.00             5.56             0.00 
##             usia penampilan_24_25        gol_24_25     assist_24_25 
##             5.56             0.00             0.00             0.00 
## nilai_pasar_juta 
##             0.00

Duplikasi Baris

sum(duplicated(pemain_raw))
## [1] 0

Duplikasi Player ID

sum(duplicated(pemain_raw$player_id))
## [1] 1

Kategori Negara

sort(unique(pemain_raw$negara))
##  [1] " England"    "Argentina"   "Belgium"     "Brazil"      "Egypt"      
##  [6] "England"     "France"      "Norway"      "Poland"      "Portugal"   
## [11] "South Korea" "Spain"

Kategori Posisi

sort(unique(pemain_raw$posisi))
## [1] "AM"                 "Attacking Midfield" "CM"                
## [4] "DM"                 "forward"            "Forward"           
## [7] "LW"                 "RW"                 "ST"

Rentang Usia

range(
  pemain_raw$usia,
  na.rm = TRUE
)
## [1] 17 40

Rentang Nilai Pasar

range(
  pemain_raw$nilai_pasar_juta,
  na.rm = TRUE
)
## [1]   12 9999

Membuat Fungsi Ringkasan Kualitas

audit_data <- function(data) {

  data.frame(
    atribut = names(data),
    tipe = sapply(data, class),
    jumlah_missing = sapply(
      data,
      function(x) sum(is.na(x))
    ),
    persen_missing = round(
      sapply(
        data,
        function(x) mean(is.na(x)) * 100
      ),
      2
    ),
    jumlah_unik = sapply(
      data,
      function(x) length(unique(x))
    ),
    row.names = NULL
  )
}

audit_awal <- audit_data(pemain_raw)

audit_awal

Implementasi Data Cleaning

Membuat Salinan Kerja

pemain <- pemain_raw

Membersihkan Spasi dan Kapitalisasi

Menghapus Spasi

pemain$negara <- trimws(pemain$negara)
pemain$posisi <- trimws(pemain$posisi)

Menyeragamkan Huruf

pemain$negara <- tools::toTitleCase(
  tolower(pemain$negara)
)

pemain$posisi <- tolower(
  pemain$posisi
)

Melihat Kategori Negara

sort(unique(pemain$negara))
##  [1] "Argentina"   "Belgium"     "Brazil"      "Egypt"       "England"    
##  [6] "France"      "Norway"      "Poland"      "Portugal"    "South Korea"
## [11] "Spain"

Melihat Kategori Posisi

sort(unique(pemain$posisi))
## [1] "am"                 "attacking midfield" "cm"                
## [4] "dm"                 "forward"            "lw"                
## [7] "rw"                 "st"

Menyeragamkan Kategori

Standardisasi Negara

pemain$negara[pemain$negara == "South korea"] <- "South Korea"

Standardisasi Posisi

pemain$posisi <- ifelse(
  pemain$posisi %in% c("rw", "right winger"),
  "Right Winger",
  ifelse(
    pemain$posisi %in% c("lw", "left winger"),
    "Left Winger",
    ifelse(
      pemain$posisi %in% c("st", "forward"),
      "Striker",
      ifelse(
        pemain$posisi %in% c("am", "attacking midfield"),
        "Attacking Midfielder",
        ifelse(
          pemain$posisi == "cm",
          "Central Midfielder",
          ifelse(
            pemain$posisi == "dm",
            "Defensive Midfielder",
            "Other"
          )
        )
      )
    )
  )
)

Hasil Standardisasi Negara

sort(unique(pemain$negara))
##  [1] "Argentina"   "Belgium"     "Brazil"      "Egypt"       "England"    
##  [6] "France"      "Norway"      "Poland"      "Portugal"    "South Korea"
## [11] "Spain"

Hasil Standardisasi Posisi

sort(unique(pemain$posisi))
## [1] "Attacking Midfielder" "Central Midfielder"   "Defensive Midfielder"
## [4] "Left Winger"          "Right Winger"         "Striker"

Mendeteksi dan Menghapus Duplikasi

Menampilkan Player ID yang Berulang

pemain$player_id[
  duplicated(pemain$player_id)
]
## [1] "P017"

Menghapus Duplikasi

pemain <- pemain[
  !duplicated(pemain$player_id),
]

rownames(pemain) <- NULL

Memeriksa Aturan Domain

Pemeriksaan Usia

pemain[
  pemain$usia < 15 | pemain$usia > 50,
  c("nama", "usia")
]

Pemeriksaan Nilai Pasar Negatif

pemain[
  pemain$nilai_pasar_juta < 0,
  c("nama", "nilai_pasar_juta")
]

Pemeriksaan Statistik Negatif

pemain[
  pemain$gol_24_25 < 0 |
    pemain$assist_24_25 < 0 |
    pemain$penampilan_24_25 < 0,
  c(
    "nama",
    "penampilan_24_25",
    "gol_24_25",
    "assist_24_25"
  )
]

Menandai Nilai Usia yang Tidak Masuk Akal

pemain$usia[
  pemain$usia < 15 | pemain$usia > 50
] <- NA

Menandai Nilai Pasar yang Tidak Masuk Akal

pemain$nilai_pasar_juta[
  pemain$nilai_pasar_juta < 0
] <- NA

Nilai pasar ekstrem yang masih positif tidak otomatis dihapus pada tahap ini, karena nilai ekstrem dapat merupakan observasi yang valid. Nilai tersebut akan dievaluasi kembali pada tahap deteksi outlier.

Memeriksa Hasil Koreksi

pemain[
  ,
  c(
    "nama",
    "usia",
    "penampilan_24_25",
    "gol_24_25",
    "assist_24_25"
  )
]

Membuat Log Perubahan

log_perubahan <- data.frame(
  tahap = character(),
  variabel = character(),
  jumlah_perubahan = numeric(),
  keterangan = character(),
  stringsAsFactors = FALSE
)

log_perubahan

Penanganan Missing Values

Mengidentifikasi Lokasi Missing Value

Jumlah Missing Value

colSums(is.na(pemain))
##        player_id             nama           negara           posisi 
##                0                0                1                0 
##             usia penampilan_24_25        gol_24_25     assist_24_25 
##                1                0                0                0 
## nilai_pasar_juta 
##                0

Menampilkan Baris yang Memiliki Missing Value

pemain[
  !complete.cases(pemain),
  c(
    "nama",
    "usia",
    "penampilan_24_25",
    "gol_24_25",
    "assist_24_25",
    "nilai_pasar_juta"
  )
]

Strategi 1 — Menghapus Baris

Persentase Data yang Hilang

mean(
  !complete.cases(pemain)
) * 100
## [1] 11.76471
pemain_hapus <- pemain[
  complete.cases(pemain),
]

nrow(pemain_hapus)
## [1] 15

Strategi 2 — Imputasi Mean dan Median

Mean Usia

mean_usia <- round(
  mean(
    pemain$usia,
    na.rm = TRUE
  )
)

mean_usia
## [1] 28

Nilai mean juga dibulatkan ke bilangan tahun terdekat agar konsisten dengan representasi usia pemain sebagai bilangan bulat.

Median Usia

median_usia <- round(
  median(
    pemain$usia,
    na.rm = TRUE
  )
)

median_usia
## [1] 28

Catatan pembulatan usia: usia pemain dinyatakan dalam satuan tahun dan secara praktis dicatat sebagai bilangan bulat. Karena median awal dapat menghasilkan nilai desimal (misalnya 27,5 tahun), nilai median dibulatkan ke bilangan tahun terdekat sebelum digunakan untuk imputasi. Dengan demikian, nilai usia hasil imputasi tetap konsisten dengan format usia pemain lain, misalnya 28, 32, 34, atau 25 tahun.

Imputasi Usia dengan Median

pemain$usia_imputasi <- pemain$usia

pemain$usia_imputasi[
  is.na(pemain$usia_imputasi)
] <- median_usia

Mean Nilai Pasar

mean_nilai <- mean(
  pemain$nilai_pasar_juta,
  na.rm = TRUE
)

mean_nilai
## [1] 691.5294

Median Nilai Pasar

median_nilai <- median(
  pemain$nilai_pasar_juta,
  na.rm = TRUE
)

median_nilai
## [1] 130

Imputasi Nilai Pasar dengan Median

pemain$nilai_pasar_imputasi <-
  pemain$nilai_pasar_juta

pemain$nilai_pasar_imputasi[
  is.na(pemain$nilai_pasar_imputasi)
] <- median_nilai

Membandingkan Nilai

pemain[
  ,
  c(
    "nama",
    "usia",
    "usia_imputasi",
    "nilai_pasar_juta",
    "nilai_pasar_imputasi"
  )
]

Strategi 3 — Imputasi Nilai Kategorik

Karena variabel kategorik memiliki jumlah kategori terbatas, modus dapat digunakan sebagai salah satu strategi sederhana.

modus <- function(x) {
  x <- x[!is.na(x)]
  ux <- unique(x)
  ux[
    which.max(
      tabulate(
        match(x, ux)
      )
    )
  ]
}

Frekuensi Negara

table(pemain$negara, useNA = "ifany")
## 
##   Argentina     Belgium      Brazil       Egypt     England      France 
##           1           1           1           1           4           1 
##      Norway      Poland    Portugal South Korea       Spain        <NA> 
##           1           1           1           1           3           1

Imputasi Negara

pemain$negara_imputasi <- pemain$negara

pemain$negara_imputasi[
  is.na(pemain$negara_imputasi)
] <- modus(
  pemain$negara_imputasi
)

table(pemain$negara_imputasi)
## 
##   Argentina     Belgium      Brazil       Egypt     England      France 
##           1           1           1           1           5           1 
##      Norway      Poland    Portugal South Korea       Spain 
##           1           1           1           1           3

Menambahkan Indikator Missing

pemain$usia_missing <- ifelse(
  is.na(pemain$usia),
  1,
  0
)

pemain$nilai_missing <- ifelse(
  is.na(pemain$nilai_pasar_juta),
  1,
  0
)

Membandingkan Distribusi Sebelum dan Sesudah Imputasi

summary(
  pemain$usia
)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   17.00   23.75   27.50   28.38   33.00   40.00       1
summary(
  pemain$usia_imputasi
)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   17.00   24.00   28.00   28.35   33.00   40.00

Penanganan Outlier

Visualisasi dengan Boxplot

boxplot(
  pemain$usia_imputasi,
  main = "Boxplot Usia Pemain",
  ylab = "Usia"
)

boxplot(
  pemain$nilai_pasar_imputasi,
  main = "Boxplot Nilai Pasar Pemain",
  ylab = "Nilai Pasar (Juta Euro)"
)

Menghitung Batas IQR

batas_iqr <- function(x) {

  q1 <- quantile(
    x,
    0.25,
    na.rm = TRUE
  )

  q3 <- quantile(
    x,
    0.75,
    na.rm = TRUE
  )

  iqr <- IQR(
    x,
    na.rm = TRUE
  )

  c(
    lower = q1 - 1.5 * iqr,
    upper = q3 + 1.5 * iqr
  )
}
batas_usia <- batas_iqr(
  pemain$usia_imputasi
)

batas_nilai <- batas_iqr(
  pemain$nilai_pasar_imputasi
)

batas_usia
## lower.25% upper.75% 
##      10.5      46.5
batas_nilai
## lower.25% upper.75% 
##    -182.5     397.5

Menandai Kandidat Outlier

Menampilkan Kandidat Outlier

Kandidat outlier ditampilkan sebagai baris data yang memenuhi batas IQR. which() digunakan agar nilai NA tidak ikut menghasilkan baris kosong pada tabel output.

idx_outlier_usia <- which(
  !is.na(pemain$usia_imputasi) &
    (
      pemain$usia_imputasi < batas_usia["lower"] |
        pemain$usia_imputasi > batas_usia["upper"]
    )
)

outlier_usia <- pemain[
  idx_outlier_usia,
  c("nama", "usia_imputasi")
]

outlier_usia
idx_outlier_nilai <- which(
  !is.na(pemain$nilai_pasar_imputasi) &
    (
      pemain$nilai_pasar_imputasi < batas_nilai["lower"] |
        pemain$nilai_pasar_imputasi > batas_nilai["upper"]
    )
)

outlier_nilai <- pemain[
  idx_outlier_nilai,
  c("nama", "nilai_pasar_imputasi")
]

outlier_nilai

Winsorization

winsor <- function(x) {

  batas <- quantile(
    x,
    c(0.05, 0.95),
    na.rm = TRUE
  )

  x[
    x < batas[1]
  ] <- batas[1]

  x[
    x > batas[2]
  ] <- batas[2]

  x
}

Membandingkan Nilai Outlier

pemain$nilai_pasar_winsor <-
  winsor(
    pemain$nilai_pasar_imputasi
  )

pemain[
  ,
  c(
    "nama",
    "nilai_pasar_imputasi",
    "nilai_pasar_winsor"
  )
]

Transformasi Data

Normalisasi Min-Max

Membuat Fungsi Min-Max

minmax <- function(x) {

  (x - min(x, na.rm = TRUE)) /
    (
      max(x, na.rm = TRUE) -
        min(x, na.rm = TRUE)
    )
}

Transformasi Usia

pemain$usia_minmax <-
  minmax(
    pemain$usia_imputasi
  )

Transformasi Nilai Pasar

pemain$nilai_minmax <-
  minmax(
    pemain$nilai_pasar_winsor
  )

Hasil Normalisasi

pemain[
  ,
  c(
    "nama",
    "usia_minmax",
    "nilai_minmax"
  )
]

Normalisasi Z-Score

Z-Score Usia

pemain$usia_z <-
  as.numeric(
    scale(
      pemain$usia_imputasi
    )
  )

Z-Score Nilai Pasar

pemain$nilai_z <-
  as.numeric(
    scale(
      pemain$nilai_pasar_winsor
    )
  )

Hasil Z-Score

pemain[
  ,
  c(
    "nama",
    "usia_z",
    "nilai_z"
  )
]

Decimal Scaling

decimal_scale <- function(x) {

  j <- ceiling(
    log10(
      max(
        abs(x),
        na.rm = TRUE
      )
    )
  )

  x / (10 ^ j)
}

Transformasi Decimal Scaling

pemain$nilai_decimal <-
  decimal_scale(
    pemain$nilai_pasar_winsor
  )

Rentang Hasil

range(
  pemain$nilai_decimal,
  na.rm = TRUE
)
## [1] 0.00144 0.21598

Membandingkan Metode Transformasi

transformasi <- pemain[
  ,
  c(
    "nama",
    "usia_imputasi",
    "nilai_pasar_imputasi",
    "nilai_pasar_winsor",
    "usia_minmax",
    "nilai_minmax",
    "usia_z",
    "nilai_z",
    "nilai_decimal"
  )
]

transformasi

Dampak Outlier terhadap Normalisasi

pemain$nilai_minmax_asli <-
  minmax(
    pemain$nilai_pasar_imputasi
  )

pemain$nilai_minmax_winsor <-
  minmax(
    pemain$nilai_pasar_winsor
  )

Penting: plot() dan abline() berada dalam chunk yang sama agar proses Knit tidak mengalami error plot.new has not been called yet.

plot(
  pemain$nilai_minmax_asli,
  pemain$nilai_minmax_winsor,
  pch = 19,
  xlab = "Min-Max Nilai Pasar Sebelum Winsorization",
  ylab = "Min-Max Nilai Pasar Setelah Winsorization",
  main = "Dampak Outlier terhadap Normalisasi"
)

abline(
  a = 0,
  b = 1,
  lty = 2
)

Integrasi Data

Memeriksa Kunci pada Kedua Sumber

Duplikasi pada Data Pemain

sum(
  duplicated(
    pemain$player_id
  )
)
## [1] 0

Duplikasi pada Data Statistik

sum(
  duplicated(
    statistik_raw$id_pemain
  )
)
## [1] 0

Pemain yang Tidak Memiliki Statistik Tambahan

setdiff(
  pemain$player_id,
  statistik_raw$id_pemain
)
## [1] "P017"

Statistik yang Tidak Memiliki Data Pemain

setdiff(
  statistik_raw$id_pemain,
  pemain$player_id
)
## [1] "P018"

Menyelaraskan Nama Identifier

statistik <- statistik_raw

names(statistik)[
  names(statistik) == "id_pemain"
] <- "player_id"

names(statistik)
## [1] "player_id"     "menit_bermain" "rating"

Melakukan Integrasi dengan Merge

data_terintegrasi <- merge(
  pemain,
  statistik,
  by = "player_id",
  all.x = TRUE
)

data_terintegrasi

Melihat Hasil Integrasi

data_terintegrasi[
  ,
  c(
    "player_id",
    "nama",
    "penampilan_24_25",
    "gol_24_25",
    "assist_24_25",
    "menit_bermain",
    "rating"
  )
]

all.x = TRUE mempertahankan seluruh pemain dari tabel utama, termasuk pemain yang tidak memiliki pasangan pada tabel statistik tambahan.

Memvalidasi Hasil Integrasi

Membandingkan Jumlah Baris

c(
  sebelum = nrow(pemain),
  sesudah = nrow(data_terintegrasi)
)
## sebelum sesudah 
##      17      17

Memeriksa Duplikasi

sum(
  duplicated(
    data_terintegrasi$player_id
  )
)
## [1] 0

Missing Value pada Variabel Statistik

colSums(
  is.na(
    data_terintegrasi[
      ,
      c(
        "menit_bermain",
        "rating"
      )
    ]
  )
)
## menit_bermain        rating 
##             1             1

Menampilkan Pemain Tanpa Statistik Tambahan

data_terintegrasi[
  is.na(
    data_terintegrasi$menit_bermain
  ),
  c(
    "player_id",
    "nama",
    "menit_bermain",
    "rating"
  )
]

Menentukan Penanganan NA pada Statistik

Membuat Variabel Final Statistik

data_terintegrasi$menit_final <-
  data_terintegrasi$menit_bermain

data_terintegrasi$rating_final <-
  data_terintegrasi$rating

Mengganti NA dengan Median

median_menit <- median(
  data_terintegrasi$menit_final,
  na.rm = TRUE
)

median_rating <- median(
  data_terintegrasi$rating_final,
  na.rm = TRUE
)

data_terintegrasi$menit_final[
  is.na(
    data_terintegrasi$menit_final
  )
] <- median_menit

data_terintegrasi$rating_final[
  is.na(
    data_terintegrasi$rating_final
  )
] <- median_rating

Memeriksa Hasil

data_terintegrasi[
  ,
  c(
    "nama",
    "menit_bermain",
    "menit_final",
    "rating",
    "rating_final"
  )
]

Dataset Akhir dan Evaluasi

Memilih Atribut Akhir

data_final <- data_terintegrasi[
  ,
  c(
    "player_id",
    "nama",
    "negara_imputasi",
    "posisi",
    "usia_imputasi",
    "penampilan_24_25",
    "gol_24_25",
    "assist_24_25",
    "nilai_pasar_winsor",
    "menit_final",
    "rating_final"
  )
]

Mengubah Nama Variabel

names(data_final) <- c(
  "player_id",
  "nama",
  "negara",
  "posisi",
  "usia",
  "penampilan",
  "gol",
  "assist",
  "nilai_pasar_juta",
  "menit_bermain",
  "rating"
)

names(data_final)
##  [1] "player_id"        "nama"             "negara"           "posisi"          
##  [5] "usia"             "penampilan"       "gol"              "assist"          
##  [9] "nilai_pasar_juta" "menit_bermain"    "rating"

Melihat Dataset Final

data_final

Audit Akhir

Memeriksa Duplikasi Player ID

sum(
  duplicated(
    data_final$player_id
  )
)
## [1] 0

Memeriksa Missing Value

colSums(
  is.na(data_final)
)
##        player_id             nama           negara           posisi 
##                0                0                0                0 
##             usia       penampilan              gol           assist 
##                0                0                0                0 
## nilai_pasar_juta    menit_bermain           rating 
##                0                0                0

Membandingkan Kondisi Sebelum dan Sesudah

perbandingan <- data.frame(
  kondisi = c(
    "Sebelum preprocessing",
    "Sesudah preprocessing"
  ),
  jumlah_baris = c(
    nrow(pemain_raw),
    nrow(data_final)
  ),
  jumlah_kolom = c(
    ncol(pemain_raw),
    ncol(data_final)
  ),
  jumlah_missing = c(
    sum(is.na(pemain_raw)),
    sum(is.na(data_final))
  ),
  jumlah_duplikat_id = c(
    sum(duplicated(pemain_raw$player_id)),
    sum(duplicated(data_final$player_id))
  )
)

perbandingan

Menyimpan Hasil

Menyimpan Dataset Final

write.csv(
  data_final,
  "data_pemain_sepakbola_setelah_preprocessing.csv",
  row.names = FALSE
)

Menyimpan Log Perubahan

write.csv(
  log_perubahan,
  "log_perubahan_preprocessing_sepakbola.csv",
  row.names = FALSE
)

Pertanyaan Refleksi

1. Apakah pemain dengan nilai pasar tinggi selalu memiliki performa terbaik?

Tidak selalu. Nilai pasar merupakan indikator ekonomi yang dipengaruhi usia, potensi, durasi kontrak, posisi, popularitas, dan kondisi pasar. Karena itu, nilai pasar tidak dapat digunakan sebagai satu-satunya ukuran performa.

2. Mengapa outlier tidak boleh otomatis dihapus?

Outlier dapat merupakan kesalahan data, tetapi juga dapat merupakan observasi yang benar. Dalam dataset pemain sepak bola, pemain dengan jumlah gol sangat tinggi atau nilai pasar sangat besar belum tentu merupakan kesalahan.

3. Mengapa missing value pada data pemain perlu diperiksa terlebih dahulu?

Missing value dapat muncul karena data tidak tersedia, pemain tidak memiliki statistik pada sumber tertentu, atau terjadi kesalahan pencatatan. Strategi penghapusan atau imputasi harus disesuaikan dengan konteks.

4. Apa risiko melakukan standardisasi kategori secara sembarangan?

Standardisasi yang salah dapat menggabungkan kategori yang sebenarnya berbeda. Misalnya posisi CM tidak boleh secara otomatis dianggap sama dengan DM karena keduanya memiliki makna taktis yang berbeda.

5. Mengapa identifier pemain harus unik sebelum integrasi?

Identifier yang tidak unik dapat menyebabkan satu pemain bergabung dengan beberapa baris data sehingga jumlah baris meningkat secara tidak semestinya dan menghasilkan dataset yang salah.

Ringkasan

Alur preprocessing yang telah dilakukan:

  1. memahami struktur dan kualitas awal dataset pemain;
  2. membersihkan spasi dan kategori;
  3. memeriksa dan menghapus duplikasi;
  4. memeriksa aturan domain;
  5. menangani missing values;
  6. mendeteksi dan menangani outlier;
  7. melakukan transformasi numerik;
  8. mengintegrasikan data statistik tambahan;
  9. melakukan audit dataset akhir; dan
  10. menyimpan dataset final serta log perubahan.

Kesimpulan: preprocessing pada dataset sepak bola bukan sekadar menghilangkan data yang terlihat aneh. Setiap perubahan harus memiliki alasan yang jelas berdasarkan struktur data, konteks statistik pemain, dan tujuan analisis.

Daftar Pustaka

  • Transfermarkt. Profil dan statistik pemain sepak bola, termasuk data penampilan, gol, posisi, usia, tinggi, dan nilai pasar.
  • Han, J., Kamber, M., & Pei, J. (2012). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann. Chapter 3: Data Preprocessing.