Schema adalah cetak biru struktur DataFrame, yaitu daftar nama kolom beserta tipe datanya. Topik ini membahas dua cara memperoleh schema pada pembacaan data, yaitu inferensi yang menebak tipe dari isi berkas dan deklarasi eksplisit yang menetapkannya secara tertulis. Jebakan dan biaya masing-masing juga dibahas. Setelah membacanya, pilihan antara keduanya dapat diputuskan secara sadar, dan schema eksplisit dapat ditulis dengan StructType maupun string DDL.
Isi sebuah schema#
Setiap kolom pada schema membawa tiga informasi, yaitu nama, tipe data, dan penanda nullable yang menyatakan apakah kolom boleh berisi nilai kosong. Kumpulan kolom tersebut membentuk objek StructType, dan setiap kolomnya adalah objek StructField. Pada PySpark, schema yang sama dapat ditulis dalam dua bentuk yang setara, seperti pada fragmen berikut.
# tidak-dijalankan
from pyspark.sql.types import IntegerType, StringType, StructField, StructType
# bentuk pertama: StructType beserta StructField
sch = StructType([
StructField("nama", StringType(), True),
StructField("kode_pos", StringType(), True),
StructField("nilai", IntegerType(), True),
])
# bentuk kedua: string DDL dengan makna yang sama
ddl = "nama string, kode_pos string, nilai int"Bentuk StructType memberi kendali penuh, termasuk penanda nullable per kolom, dan sesuai untuk schema yang panjang atau dirakit secara dinamis. String DDL lebih ringkas dan mudah dibaca pada skrip pendek. Keduanya dapat diterima oleh spark.read.schema(), dan hasilnya diperiksa pada skrip utama topik ini.
Inferensi: pembaca menebak tipe#
Dengan .option("inferSchema", "true"), pembaca CSV menyapu data untuk menebak tipe setiap kolom, misalnya mengenali kolom yang isinya angka sebagai integer. Cara ini nyaman untuk eksplorasi cepat karena skripnya pendek. Tebakan itu pun murni berpegang pada isi data, dan di situlah jebakannya. Angka yang berawalan nol, seperti kode pos 02150, terbaca sebagai bilangan sehingga nol di depannya hilang dan menyisakan 2150. Nilai kosong membuat kolom menjadi nullable, dan kolom yang kebetulan seluruh isinya angka tetapi seharusnya teks, seperti kode pos atau nomor telepon, salah tipe tanpa pesan apa pun.
Kesalahan tipe semacam ini tidak berhenti di tampilan. Perbandingan, pengurutan, dan join dilakukan dengan tipe yang salah, sehingga hasil yang tampak wajar sebenarnya sudah menyimpang. Karena tidak ada error yang muncul, penyimpangan baru disadari saat data sudah dipakai lebih jauh.
Menguji jebakan dan versi eksplisitnya#
Skrip berikut menulis berkas CSV kecil berisi kolom nama, kode pos, dan nilai ujian ke /tmp, lalu membacanya dengan tiga schema yang berbeda. Pembacaan pertama mengandalkan inferensi, pembacaan kedua memakai schema eksplisit berbentuk StructType, dan pembacaan ketiga memakai schema eksplisit berbentuk string DDL.
from pathlib import Path
from pyspark.sql import SparkSession
from pyspark.sql.types import IntegerType, StringType, StructField, StructType
csv_path = "/tmp/lokasi/kode_pos.csv"
Path("/tmp/lokasi").mkdir(parents=True, exist_ok=True)
Path(csv_path).write_text(
"nama,kode_pos,nilai\n"
"Ali,10210,85\n"
"Budi,02150,90\n"
"Citra,,78\n"
"Dewi,10210,92\n",
encoding="utf-8",
)
spark = (
SparkSession.builder
.master("local[2]")
.appName("uji-schema")
.getOrCreate()
)
print("=== inferensi ===")
infer = spark.read.option("header", "true").option("inferSchema", "true").csv(csv_path)
infer.printSchema()
infer.orderBy("nama").show()
print("=== eksplisit dengan StructType ===")
sch = StructType([
StructField("nama", StringType(), False),
StructField("kode_pos", StringType(), True),
StructField("nilai", IntegerType(), True),
])
eksplisit = spark.read.schema(sch).option("header", "true").csv(csv_path)
eksplisit.printSchema()
eksplisit.orderBy("nama").show()
print("=== eksplisit dengan DDL string ===")
ddl = spark.read.schema("nama string, kode_pos string, nilai int").option("header", "true").csv(csv_path)
ddl.printSchema()
spark.stop()WARNING: Using incubator modules: jdk.incubator.vector
=== inferensi ===
root
|-- nama: string (nullable = true)
|-- kode_pos: integer (nullable = true)
|-- nilai: integer (nullable = true)
+-----+--------+-----+
| nama|kode_pos|nilai|
+-----+--------+-----+
| Ali| 10210| 85|
| Budi| 2150| 90|
|Citra| NULL| 78|
| Dewi| 10210| 92|
+-----+--------+-----+
=== eksplisit dengan StructType ===
root
|-- nama: string (nullable = true)
|-- kode_pos: string (nullable = true)
|-- nilai: integer (nullable = true)
+-----+--------+-----+
| nama|kode_pos|nilai|
+-----+--------+-----+
| Ali| 10210| 85|
| Budi| 02150| 90|
|Citra| NULL| 78|
| Dewi| 10210| 92|
+-----+--------+-----+
=== eksplisit dengan DDL string ===
root
|-- nama: string (nullable = true)
|-- kode_pos: string (nullable = true)
|-- nilai: integer (nullable = true)Hasil inferensi menunjukkan kode_pos bertipe integer, dan kode pos Budi yang aslinya 02150 tercetak 2150 karena nol di depannya dianggap tidak bermakna bagi bilangan. Nilai kosong pada baris Citra menjadi NULL. Pada pembacaan eksplisit, kode_pos dideklarasikan sebagai string, sehingga 02150 utuh tersimpan. Bagian terakhir membuktikan bahwa StructType dan string DDL menghasilkan schema yang sama persis.
Biaya inferensi pada data besar#
Inferensi berarti membaca data sekali lagi hanya untuk menentukan tipe. Pada CSV dengan inferSchema, pembaca melakukan satu pass tambahan untuk memindai berkas dan mendeteksi tipe sebelum pembacaan yang sesungguhnya, sehingga berkas besar praktis dibaca dua kali. Pada JSON, pengambilan sampel schema berjalan pada setiap pembacaan karena formatnya tidak menyimpan schema. Sebaliknya, schema eksplisit tidak menambah pass apa pun, karena pembaca langsung memetakan isi berkas ke tipe yang dideklarasikan.
Biaya itu membesar seiring data. Inferensi pada data besar menghabiskan satu pass pemrosesan berkas penuh yang sama sekali tidak menghasilkan keluaran bagi pekerjaan data itu sendiri. Schema eksplisit bekerja lebih murah sekaligus mendokumentasikan maksud penulis data, karena struktur berkas tertulis pada kode dan berubah menjadi kontrak yang jelas.
Kesalahan umum#
Kesalahan pertama adalah mengandalkan inferensi pada data yang dipakai untuk produksi. Inferensi memang pas untuk eksplorasi, tetapi tebakan tipe yang bergantung pada isi data berubah seiring isi berkasnya. Satu nilai tak terduga, misalnya kode pos baru yang berawalan huruf, dapat mengubah tipe kolom pada pembacaan berikutnya dan mematahkan pipeline yang sudah berjalan.
Kesalahan kedua adalah lupa .option("header", "true") saat membaca CSV dengan schema eksplisit. Tanpa opsi itu, baris header ikut dianggap data, dan teks nilai pada baris tersebut gagal dikonversi menjadi bilangan. Dalam mode PERMISSIVE, kegagalan itu tidak memunculkan error, tetapi menghasilkan satu baris berisi nilai kosong yang sulit dilacak.
Kesalahan ketiga adalah menganggap nullable = false pada StructField akan ditegakkan saat membaca berkas. Pembaca CSV tetap melaporkan semua kolom nullable seperti pada keluaran skrip di atas. Penegakan nullability memang melekat pada sumber data yang mampu menjaminnya, bukan pada berkas teks yang isinya tidak terjamin.
Ringkasan#
- Schema terdiri atas nama kolom, tipe data, dan penanda nullable, dan tertulis dalam bentuk
StructTypeatau string DDL yang setara. - Inferensi menebak tipe dari isi data, sehingga angka berawalan nol kehilangan nolnya, nilai kosong membuat kolom nullable, dan kolom teks berisi angka salah tipe tanpa pesan.
- Inferensi menambah satu pass penuh atas data pada CSV, sedangkan schema eksplisit melalui
spark.read.schema()langsung dipatuhi tanpa pass tambahan. - Kolom schema eksplisit dicocokkan berdasarkan posisi pada CSV, sehingga urutan dan jumlah kolom harus benar.
Referensi#
- pyspark.sql.types.StructType — Apache Spark — Referensi objek StructType dan StructField sebagai pembawa schema eksplisit.
- pyspark.sql.DataFrameReader — Apache Spark — Dokumentasi metode schema pada pembaca DataFrame, termasuk penerimaan string DDL.
- Data Sources — Apache Spark — Panduan sumber data yang menjelaskan schema eksplisit dan opsi pembacaan.
- CSV Files — Apache Spark — Daftar opsi CSV seperti inferSchema, header, dan mode penanganan baris rusak.