Argomenti della lezione
  1. 1. Mostrare le prime n righe
  2. 2. Valore mancante
  3. 3. Contare le righe
  4. 4. Elencare i nomi delle colonne
  5. 5. Colonna dimensione
  6. 6. Colonna misura
  7. 7. Identificativo numerico e misura
  8. 8. Controllare la grana
  9. 9. Doppio conteggio da una grana sbagliata
  10. 10. Descrivere una tabella prima di usarla
  11. 11. Formato lungo
  12. 12. Formato largo
  13. 13. Scegliere la forma
  14. 14. Portare tutte le righe nel driver
  15. 15. Stampare lo schema
  16. 16. Struttura della tabella
  17. 17. Data letta come testo
  18. 18. Limitare il numero di righe
  19. 19. Portare n righe nel driver
  20. 20. Mostrare il testo completo
  21. 21. Mostrare un campo per riga
  22. 22. Controllare se una tabella è vuota
  23. 23. Statistiche descrittive
  24. 24. Statistiche con quartili
  25. 25. Quantili approssimati
  26. 26. Correlazione tra due colonne
  27. 27. Covarianza tra due colonne
  28. 28. Valori frequenti
  29. 29. Domande prima del codice
  30. 30. Ispezionare una nuova tabella
  31. Verifica la comprensione
  32. Risposte

Scarica il notebook

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 == 2

Verifica la comprensione

  1. Quali campi identificano una riga d’ordine del bar?
  2. Perché la somma dei totali d’ordine alla grana della riga è troppo alta?
  3. Come descriveresti la copertura delle durate nei dati di consegna?

Risposte

  1. La coppia order_id e line_id.
  2. Un ordine con più righe contribuisce con l’intero totale una volta per ogni riga. Sommando un solo totale per ordine ottieni 54.
  3. Due consegne su tre hanno una durata nota. Il null indica una durata sconosciuta, quindi sostituirlo con zero cambierebbe il significato dei dati.