Argomenti della lezione
- 1. Mostrare le prime n righe
- 2. Valore mancante
- 3. Contare le righe
- 4. Elencare i nomi delle colonne
- 5. Colonna dimensione
- 6. Colonna misura
- 7. Identificativo numerico e misura
- 8. Controllare la grana
- 9. Doppio conteggio da una grana sbagliata
- 10. Descrivere una tabella prima di usarla
- 11. Formato lungo
- 12. Formato largo
- 13. Scegliere la forma
- 14. Portare tutte le righe nel driver
- 15. Stampare lo schema
- 16. Struttura della tabella
- 17. Data letta come testo
- 18. Limitare il numero di righe
- 19. Portare n righe nel driver
- 20. Mostrare il testo completo
- 21. Mostrare un campo per riga
- 22. Controllare se una tabella è vuota
- 23. Statistiche descrittive
- 24. Statistiche con quartili
- 25. Quantili approssimati
- 26. Correlazione tra due colonne
- 27. Covarianza tra due colonne
- 28. Valori frequenti
- 29. Domande prima del codice
- 30. Ispezionare una nuova tabella
- Verifica la comprensione
- Risposte
03 · Ispezionare una tabella
Descrivi una nuova tabella Spark prima di usarla. Parti da una piccola tabella di righe d’ordine del bar e finisci con una tabella di consegne. Esegui tutte le celle in una nuova sessione Python su Kaggle con PySpark classico 4.x e Java 17 o successivo. Se serve, esegui %pip install "pyspark>=4.0,<4.1" in una cella di preparazione con Internet abilitato. Tutti i dati sono definiti nel notebook.
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
spark = SparkSession.builder.master("local[2]").appName("03-inspect-a-table").getOrCreate()
lines = spark.createDataFrame(
[
(101, 1, "tea", 2, 9.00, "2026-11-01", "Two teas, one with lemon"),
(101, 2, "cake", 1, 9.00, "2026-11-01", None),
(102, 1, "coffee", 3, 12.00, "2026-11-02", "Collect from the counter"),
(103, 1, "tea", 4, 15.00, "2026-11-02", None),
(103, 2, "coffee", 2, 15.00, "2026-11-02", "One decaf coffee"),
(104, 1, "tea", 5, 18.00, "2026-11-03", "Serve in takeaway cups"),
],
"order_id INT, line_id INT, item STRING, quantity INT, order_total DOUBLE, order_date_text STRING, note STRING",
)1. Mostrare le prime n righe
show(3) stampa fino a tre righe. L’anteprima aiuta a orientarsi. Senza ordinamento Spark non garantisce l’ordine delle righe mostrate.
2. Valore mancante
Un null in Spark indica un valore assente. Una nota null è diversa da una nota che contiene una stringa vuota.
3. Contare le righe
count() restituisce il numero totale di righe, comprese quelle con valori null.
4. Elencare i nomi delle colonne
columns restituisce i nomi nell’ordine dello schema.
lines.show(3)
assert lines.count() == 6
print(lines.columns)5. Colonna dimensione
Una dimensione descrive un’osservazione o permette di raggrupparla. Qui item consente di confrontare tè, caffè e torta.
6. Colonna misura
Una misura rappresenta una quantità che puoi riepilogare con un calcolo adatto alla grana. Per esempio, la somma di quantity tra le righe dà il numero di unità vendute.
7. Identificativo numerico e misura
order_id usa interi come etichette. Contando gli ID distinti ottieni il numero di ordini. Sommare gli ID non ha significato per l’attività.
8. Controllare la grana
La grana ipotizzata è una riga per ogni riga d’ordine. Raggruppa per order_id e line_id, conta le righe di ogni gruppo e filtra i conteggi superiori a uno. Cerca anche identificativi mancanti. Qui usiamo groupBy(), filter() e count_distinct() come strumenti. Li ritroverai nelle lezioni successive.
duplicate_lines = lines.groupBy("order_id", "line_id").count().filter(F.col("count") > 1)
missing_keys = lines.filter(F.col("order_id").isNull() | F.col("line_id").isNull())
assert duplicate_lines.count() == 0
assert missing_keys.count() == 0
lines.agg(F.count_distinct("order_id").alias("orders")).show()9. Doppio conteggio da una grana sbagliata
order_total appartiene all’ordine e si ripete su ciascuna delle sue righe. Sommando alla grana della riga ottieni 78. Alla grana dell’ordine ottieni 54. Nei dati di esempio sappiamo che il totale è costante all’interno di ogni ordine, quindi max() ne conserva una copia. Con dati nuovi verifica questa ipotesi prima di usare la scorciatoia.
orders = lines.groupBy("order_id").agg(F.max("order_total").alias("order_total"))
assert lines.agg(F.sum("order_total")).first()[0] == 78
assert orders.agg(F.sum("order_total")).first()[0] == 54
orders.show()10. Descrivere una tabella prima di usarla
Indica grana, identificativi, dimensioni, misure e lacune note. Qui ogni riga d’ordine è identificata dalla coppia order_id e line_id. Il prodotto è una dimensione, la quantità è una misura della riga, il totale appartiene all’ordine e le note possono mancare.
11. Formato lungo
Nella tabella lunga delle vendite giornaliere ogni riga contiene un giorno, un prodotto e le unità vendute. Aggiungere un prodotto significa aggiungere righe.
12. Formato largo
Nella tabella larga delle vendite giornaliere ogni riga contiene un giorno e una colonna per prodotto. Aggiungere un prodotto cambia lo schema.
13. Scegliere la forma
Usa il formato lungo per confrontare o raggruppare per prodotto quando l’insieme delle categorie cambia. Il formato largo può servire per un report fisso che affianca poche categorie note. Gli esempi mostrano un solo giorno, con zero unità di caffè vendute.
daily_long = spark.createDataFrame(
[("2026-11-01", "tea", 2), ("2026-11-01", "cake", 1), ("2026-11-01", "coffee", 0)],
"day STRING, item STRING, units INT",
)
daily_wide = spark.createDataFrame(
[("2026-11-01", 2, 1, 0)],
"day STRING, tea_units INT, cake_units INT, coffee_units INT",
)
daily_long.show()
daily_wide.show()14. Portare tutte le righe nel driver
collect() porta tutte le righe in una lista Python nel driver. Riservalo a dati che sai poter contenere in memoria, come l’esempio giornaliero di tre righe.
print(daily_long.collect())15. Stampare lo schema
printSchema() stampa i nomi, i tipi e la possibilità di contenere null per ciascun campo. Se un campo ammette null, esamina i dati per vedere se ne contiene.
16. Struttura della tabella
Descrivi la struttura logica usando schema e grana. Lo schema può dirti che line_id è un intero. La definizione operativa e i controlli delle chiavi indicano che identifica una riga all’interno del suo ordine.
17. Data letta come testo
order_date_text sembra una data, ma lo schema la dichiara come stringa. Usa to_date() con il formato di input noto per creare una colonna di date. Controlla lo schema dopo la conversione.
lines.printSchema()
dated = lines.withColumn("order_date", F.to_date("order_date_text", "yyyy-MM-dd"))
dated.select("order_date_text", "order_date").printSchema()18. Limitare il numero di righe
limit(2) restituisce un DataFrame con al massimo due righe. È una trasformazione che descrive il risultato da calcolare quando serve.
19. Portare n righe nel driver
take(2) restituisce al massimo due righe in una lista Python. È un’azione. Qui orderBy() rende l’anteprima deterministica ordinando per la chiave della riga già verificata.
20. Mostrare il testo completo
truncate=False fa sì che show() stampi tutto il testo di ogni cella.
21. Mostrare un campo per riga
vertical=True stampa ogni record come un blocco con un campo per riga di output. È utile quando un record è troppo largo da leggere in orizzontale.
22. Controllare se una tabella è vuota
isEmpty() verifica se il numero di righe è zero. Una riga con tutti i valori null conta comunque come riga.
sorted_lines = lines.orderBy("order_id", "line_id")
sorted_lines.limit(2).show(truncate=False)
print(sorted_lines.take(2))
sorted_lines.show(1, truncate=False, vertical=True)
assert lines.limit(0).isEmpty()
assert not lines.isEmpty()
assert not spark.createDataFrame([(None,)], "note STRING").isEmpty()23. Statistiche descrittive
describe() restituisce conteggio, media, deviazione standard, minimo e massimo per una colonna numerica. Il conteggio della colonna esclude i null. count("*") conta le righe e count("note") conta le note presenti. In questa tabella non vuota, il rapporto tra la differenza dei conteggi e il numero di righe, moltiplicato per 100, dà la percentuale di note mancanti.
24. Statistiche con quartili
Per impostazione predefinita, summary() include anche quartili approssimati al 25%, 50% e 75%. I quartili aiutano a descrivere la distribuzione dei valori.
lines.describe("quantity").show()
lines.select("quantity").summary().show()
counts = lines.agg(F.count("*").alias("rows"), F.count("note").alias("present_notes")).first()
print(f"Missing notes: {100 * (counts.rows - counts.present_notes) / counts.rows:.1f}%")25. Quantili approssimati
approxQuantile() accetta probabilità da 0 a 1. L’errore relativo delimita la posizione del valore restituito nell’ordinamento. I null vengono ignorati.
26. Correlazione tra due colonne
corr() restituisce la correlazione di Pearson. Misura direzione e intensità di una relazione lineare su una scala da -1 a 1 per input non costanti. Qui usiamo una riga per ordine e confrontiamo le unità totali con il totale dell’ordine.
27. Covarianza tra due colonne
cov() restituisce la covarianza campionaria. Il segno indica la direzione della variazione congiunta. La grandezza dipende dalle unità di misura degli input.
28. Valori frequenti
freqItems() restituisce candidati in base alla soglia di supporto scelta e può includere falsi positivi. Usa conteggi esatti con groupBy().count() se devi riportare quali valori raggiungono la soglia.
print("Quantity quantiles:", lines.approxQuantile("quantity", [0.25, 0.5, 0.75], 0.05))
order_measures = lines.groupBy("order_id").agg(
F.sum("quantity").alias("units"), F.max("order_total").alias("order_total")
)
print("Correlation:", order_measures.corr("units", "order_total"))
print("Sample covariance:", order_measures.cov("units", "order_total"))
lines.freqItems(["item"], support=0.4).show(truncate=False)
lines.groupBy("item").count().orderBy(F.desc("count"), "item").show()29. Domande prima del codice
Che cosa rappresenta una riga? Che cosa dovrebbe identificarla? Quali campi sono misure e in quali unità? Che cosa significano i null? Qualche valore potrebbe ripetersi perché appartiene a una grana diversa?
30. Ispezionare una nuova tabella
La tabella ha una riga per consegna. Usa anteprima, schema, controlli delle chiavi e conteggi per confermarlo sui dati di esempio. Prima di calcolare una durata tipica, decidi se una durata mancante debba valere zero.
deliveries = spark.createDataFrame(
[("D1", 101, 18), ("D2", 102, None), ("D3", 104, 27)],
"delivery_id STRING, order_id INT, duration_minutes INT",
)
deliveries.orderBy("delivery_id").show()
deliveries.printSchema()
profile = deliveries.agg(
F.count("*").alias("rows"),
F.count_distinct("delivery_id").alias("distinct_deliveries"),
F.count("duration_minutes").alias("known_durations"),
).first()
assert profile.rows == profile.distinct_deliveries == 3
assert profile.known_durations == 2Verifica la comprensione
- Quali campi identificano una riga d’ordine del bar?
- Perché la somma dei totali d’ordine alla grana della riga è troppo alta?
- Come descriveresti la copertura delle durate nei dati di consegna?
Risposte
- La coppia
order_ideline_id. - Un ordine con più righe contribuisce con l’intero totale una volta per ogni riga. Sommando un solo totale per ordine ottieni 54.
- Due consegne su tre hanno una durata nota. Il null indica una durata sconosciuta, quindi sostituirlo con zero cambierebbe il significato dei dati.