| 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.
Setelah menyelesaikan praktikum ini, mahasiswa diharapkan mampu:
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.
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:
praktikum_preprocessing_sepakbola..Rmd di dalam folder proyek.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.
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_rawSumber 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## [1] "player_id" "nama" "negara" "posisi"
## [5] "usia" "penampilan_24_25" "gol_24_25" "assist_24_25"
## [9] "nilai_pasar_juta"
## '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 ...
## 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
##
## 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
## 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
## [1] " England" "Argentina" "Belgium" "Brazil" "Egypt"
## [6] "England" "France" "Norway" "Poland" "Portugal"
## [11] "South Korea" "Spain"
## [1] "AM" "Attacking Midfield" "CM"
## [4] "DM" "forward" "Forward"
## [7] "LW" "RW" "ST"
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## [1] "Argentina" "Belgium" "Brazil" "Egypt" "England"
## [6] "France" "Norway" "Poland" "Portugal" "South Korea"
## [11] "Spain"
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"
)
)
)
)
)
)## [1] "Argentina" "Belgium" "Brazil" "Egypt" "England"
## [6] "France" "Norway" "Poland" "Portugal" "South Korea"
## [11] "Spain"
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"
)
]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.
## 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
## [1] 28
Nilai mean juga dibulatkan ke bilangan tahun terdekat agar konsisten dengan representasi usia pemain sebagai bilangan bulat.
## [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.
## [1] 691.5294
## [1] 130
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)
)
)
]
}##
## 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
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
boxplot(
pemain$nilai_pasar_imputasi,
main = "Boxplot Nilai Pasar Pemain",
ylab = "Nilai Pasar (Juta Euro)"
)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
## lower.25% upper.75%
## -182.5 397.5
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_usiaidx_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_nilaitransformasi <- pemain[
,
c(
"nama",
"usia_imputasi",
"nilai_pasar_imputasi",
"nilai_pasar_winsor",
"usia_minmax",
"nilai_minmax",
"usia_z",
"nilai_z",
"nilai_decimal"
)
]
transformasipemain$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
)## [1] "P017"
## [1] "P018"
statistik <- statistik_raw
names(statistik)[
names(statistik) == "id_pemain"
] <- "player_id"
names(statistik)## [1] "player_id" "menit_bermain" "rating"
## sebelum sesudah
## 17 17
## menit_bermain rating
## 1 1
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_ratingnames(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"
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))
)
)
perbandinganTidak 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.
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.
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.
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.
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.
Alur preprocessing yang telah dilakukan:
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.