Oggi, i dati sono il motore di quasi ogni decisione, ma spesso risiedono “bloccati” dentro i database. Per chi usa Python, il problema non è se estrarli, ma come farlo in modo efficace. La combinazione di SQLite e Pandas è una delle più potenti per questo compito: il primo estrae i dati in modo chirurgico, il secondo li trasforma in insight.
Ma la teoria serve a poco senza la pratica.
Questa guida non è un elenco di comandi, ma un percorso pratico. Attraverso 6 scenari lavorativi realistici, passerai dall’interrogare un semplice database di vendite alla costruzione di un sistema di reporting automatizzato. Iniziamo dal primo scenario.
Esercizio 1: Analisi di un E-commerce Base
Scenario:
Sei un analista junior in un e-commerce di libri. Devi analizzare le vendite del primo trimestre per identificare i libri più popolari.
Soluzione:
import sqlite3
import pandas as pd
# Creazione del database e inserimento dati
conn = sqlite3.connect('libreria.db')
cursor = conn.cursor()
# Creazione tabella vendite
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite (
id INTEGER PRIMARY KEY,
titolo TEXT NOT NULL,
autore TEXT NOT NULL,
genere TEXT NOT NULL,
copie_vendute INTEGER,
mese TEXT
)
''')
# Inserimento dati di esempio
dati_vendite = [
(1, 'Il Nome del Vento', 'Patrick Rothfuss', 'Fantasy', 150, 'Gennaio'),
(2, 'Harry Potter', 'J.K. Rowling', 'Fantasy', 200, 'Gennaio'),
(3, '1984', 'George Orwell', 'Distopia', 80, 'Gennaio'),
(4, 'Il Nome del Vento', 'Patrick Rothfuss', 'Fantasy', 120, 'Febbraio'),
(5, 'Harry Potter', 'J.K. Rowling', 'Fantasy', 180, 'Febbraio'),
(6, 'Orgoglio e Pregiudizio', 'Jane Austen', 'Romanzo', 90, 'Febbraio'),
(7, 'Il Nome del Vento', 'Patrick Rothfuss', 'Fantasy', 110, 'Marzo'),
(8, '1984', 'George Orwell', 'Distopia', 95, 'Marzo'),
(9, 'Il Grande Gatsby', 'F. Scott Fitzgerald', 'Romanzo', 70, 'Marzo')]
cursor.executemany('INSERT INTO vendite VALUES (?, ?, ?, ?, ?, ?)', dati_vendite)
conn.commit()
# ANALISI CON PANDAS
# Caricamento dati in DataFrame
query = "SELECT * FROM vendite"
df_vendite = pd.read_sql_query(query, conn)
print("=== DATASET COMPLETO ===")
print(df_vendite)
print("\n")
# 1. Vendite totali per libro
vendite_per_libro = df_vendite.groupby('titolo')['copie_vendute'].sum().sort_values(ascending=False)
print("=== VENDITE TOTALI PER LIBRO ===")
print(vendite_per_libro)
print("\n")
# 2. Genere più venduto
genere_popolare = df_vendite.groupby('genere')['copie_vendute'].sum().idxmax()
vendite_genere = df_vendite.groupby('genere')['copie_vendute'].sum().max()
print(f"Genere più venduto: {genere_popolare} con {vendite_genere} copie")
conn.close()
Output atteso:
=== DATASET COMPLETO ===
id titolo autore genere copie_vendute mese
0 1 Il Nome del Vento Patrick Rothfuss Fantasy 150 Gennaio
1 2 Harry Potter J.K. Rowling Fantasy 200 Gennaio
2 3 1984 George Orwell Distopia 80 Gennaio
3 4 Il Nome del Vento Patrick Rothfuss Fantasy 120 Febbraio
4 5 Harry Potter J.K. Rowling Fantasy 180 Febbraio
5 6 Orgoglio e Pregiudizio Jane Austen Romanzo 90 Febbraio
6 7 Il Nome del Vento Patrick Rothfuss Fantasy 110 Marzo
7 8 1984 George Orwell Distopia 95 Marzo
8 9 Il Grande Gatsby F. Scott Fitzgerald Romanzo 70 Marzo
=== VENDITE TOTALI PER LIBRO ===
titolo
Il Nome del Vento 380
Harry Potter 380
1984 175
Orgoglio e Pregiudizio 90
Il Grande Gatsby 70
Name: copie_vendute, dtype: int64
Genere più venduto: Fantasy con 760 copie
Osservazioni:
💡 Il metodo groupby() permette di aggregare dati per categorie, mentre sum() calcola la somma. Combinarli è fondamentale per analisi di business.
Esercizio 2: Gestione Inventario con JOIN
Scenario:
L’e-commerce si espande e ora gestisce anche informazioni sugli editori. Devi analizzare le prestazioni degli editori.
Soluzione:
import sqlite3
import pandas as pd
conn = sqlite3.connect('libreria_avanzata.db')
cursor = conn.cursor()
# Tabelle correlate
cursor.execute('''
CREATE TABLE IF NOT EXISTS libri (
id INTEGER PRIMARY KEY,
titolo TEXT NOT NULL,
autore TEXT NOT NULL,
editore_id INTEGER,
prezzo REAL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS editori (
id INTEGER PRIMARY KEY,
nome_editore TEXT NOT NULL,
citta TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite_dettaglio (
id INTEGER PRIMARY KEY,
libro_id INTEGER,
data_vendita TEXT,
quantita INTEGER
)
''')
# Inserimento dati
editori = [
(1, 'Mondadori', 'Milano'),
(2, 'Einaudi', 'Torino'),
(3, 'Feltrinelli', 'Milano')]
libri = [
(1, 'Il Nome del Vento', 'Patrick Rothfuss', 1, 18.50),
(2, 'Harry Potter', 'J.K. Rowling', 2, 15.00),
(3, '1984', 'George Orwell', 1, 12.00),
(4, 'Orgoglio e Pregiudizio', 'Jane Austen', 3, 10.50)]
vendite = [
(1, 1, '2024-01-15', 5),
(2, 2, '2024-01-20', 3),
(3, 1, '2024-02-10', 4),
(4, 3, '2024-02-15', 2),
(5, 4, '2024-03-01', 6)]
cursor.executemany('INSERT INTO editori VALUES (?, ?, ?)', editori)
cursor.executemany('INSERT INTO libri VALUES (?, ?, ?, ?, ?)', libri)
cursor.executemany('INSERT INTO vendite_dettaglio VALUES (?, ?, ?, ?)', vendite)
conn.commit()
# ANALISI CON JOIN
query_complessa = '''
SELECT
e.nome_editore,
l.titolo,
l.autore,
l.prezzo,
v.quantita,
(l.prezzo * v.quantita) as fatturato
FROM vendite_dettaglio v
JOIN libri l ON v.libro_id = l.id
JOIN editori e ON l.editore_id = e.id
'''
df_vendite_complete = pd.read_sql_query(query_complessa, conn)
print("=== VENDITE CON DETTAGLI EDITORI ===")
print(df_vendite_complete)
print("\n")
# Fatturato per editore
fatturato_editore = df_vendite_complete.groupby('nome_editore')['fatturato'].sum()
print("=== FATTURATO PER EDITORE ===")
print(fatturato_editore)
conn.close()
Output atteso:
=== VENDITE CON DETTAGLI EDITORI ===
nome_editore titolo autore prezzo quantita fatturato
0 Mondadori Il Nome del Vento Patrick Rothfuss 18.5 5 92.5
1 Einaudi Harry Potter J.K. Rowling 15.0 3 45.0
2 Mondadori Il Nome del Vento Patrick Rothfuss 18.5 4 74.0
3 Mondadori 1984 George Orwell 12.0 2 24.0
4 Feltrinelli Orgoglio e Pregiudizio Jane Austen 10.5 6 63.0
=== FATTURATO PER EDITORE ===
nome_editore
Einaudi 45.0
Feltrinelli 63.0
Mondadori 190.5
Name: fatturato, dtype: float64
Osservazioni:
💡 Le JOIN SQL permettono di combinare dati da tabelle diverse. In Pandas, possiamo eseguire JOIN direttamente nel SQL query prima di caricare i dati nel DataFrame.
Esercizio 3: Analisi Temporale delle Vendite
Scenario:
Devi analizzare l’andamento delle vendite nel tempo per identificare stagionalità e trend.
Soluzione:
import sqlite3
import pandas as pd
from datetime import datetime
conn = sqlite3.connect('vendite_temporali.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite_temporali (
id INTEGER PRIMARY KEY,
prodotto TEXT,
data_vendita DATE,
quantita INTEGER,
prezzo_unitario REAL
)
''')
# Generazione dati temporali
vendite_data = [
(1, 'Libro A', '2024-01-05', 10, 15.00),
(2, 'Libro B', '2024-01-12', 5, 20.00),
(3, 'Libro A', '2024-01-19', 8, 15.00),
(4, 'Libro C', '2024-02-02', 12, 12.50),
(5, 'Libro B', '2024-02-09', 6, 20.00),
(6, 'Libro A', '2024-02-16', 15, 15.00),
(7, 'Libro C', '2024-03-01', 7, 12.50),
(8, 'Libro A', '2024-03-08', 11, 15.00),
(9, 'Libro B', '2024-03-15', 9, 20.00)]
cursor.executemany('INSERT INTO vendite_temporali VALUES (?, ?, ?, ?, ?)', vendite_data)
conn.commit()
# Caricamento e analisi temporale
df_temporale = pd.read_sql_query("SELECT * FROM vendite_temporali", conn)
# Conversione a datetime
df_temporale['data_vendita'] = pd.to_datetime(df_temporale['data_vendita'])
df_temporale['fatturato'] = df_temporale['quantita'] * df_temporale['prezzo_unitario']
print("=== DATASET CON DATA CONVERTITA ===")
print(df_temporale.dtypes)
print("\n")
# Vendite mensili
df_temporale['mese'] = df_temporale['data_vendita'].dt.to_period('M')
vendite_mensili = df_temporale.groupby('mese')['fatturato'].sum()
print("=== VENDITE MENSILI ===")
print(vendite_mensili)
print("\n")
# Trend prodotto A
prodotto_a = df_temporale[df_temporale['prodotto'] == 'Libro A']
trend_a = prodotto_a.groupby('mese')['quantita'].sum()
print("=== TREND LIBRO A ===")
print(trend_a)
conn.close()
Output atteso:
=== DATASET CON DATA CONVERTITA ===
id int64
prodotto object
data_vendita datetime64[ns]
quantita int64
prezzo_unitario float64
fatturato float64
dtype: object
=== VENDITE MENSILI ===
mese
2024-01 310.0
2024-02 387.5
2024-03 385.0
Freq: M, Name: fatturato, dtype: float64
=== TREND LIBRO A ===
mese
2024-01 18
2024-02 15
2024-03 11
Freq: M, Name: quantita, dtype: int64
Osservazioni:
💡 La conversione a datetime permette di utilizzare potenti funzioni di analisi temporale come dt.to_period() per raggruppare per mese/anno.
Esercizio 4: Pulizia Dati e Gestione Valori Mancanti
Scenario:
Ricevi un dataset di vendite con dati incompleti. Devi pulire i dati prima dell’analisi.
Soluzione:
import sqlite3
import pandas as pd
import numpy as np
conn = sqlite3.connect('dati_sporchi.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite_sporche (
id INTEGER PRIMARY KEY,
cliente TEXT,
prodotto TEXT,
quantita INTEGER,
prezzo REAL,
data_acquisto TEXT
)
''')
# Dati con problemi intenzionali
dati_sporchi = [
(1, 'Mario Rossi', 'Libro X', 2, 15.00, '2024-01-10'),
(2, None, 'Libro Y', 1, 20.00, '2024-01-15'), # Cliente mancante
(3, 'Luisa Verdi', 'Libro Z', None, 18.50, '2024-01-20'), # Quantità mancante
(4, 'Giulia Neri', 'Libro X', 3, None, '2024-02-05'), # Prezzo mancante
(5, 'Marco Bianchi', None, 2, 12.00, '2024-02-10'), # Prodotto mancante
(6, 'Anna Gialli', 'Libro Y', 1, 20.00, 'data_errata') # Data errata
]
cursor.executemany('INSERT INTO vendite_sporche VALUES (?, ?, ?, ?, ?, ?)', dati_sporchi)
conn.commit()
# Caricamento e pulizia
df_sporco = pd.read_sql_query("SELECT * FROM vendite_sporche", conn)
print("=== DATASET ORIGINALE ===")
print(df_sporco)
print(f"Shape originale: {df_sporco.shape}")
print("\n")
# 1. Identificazione valori mancanti
valori_mancanti = df_sporco.isnull().sum()
print("=== VALORI MANCANTI ===")
print(valori_mancanti)
print("\n")
# 2. Pulizia strategica
df_pulito = df_sporco.copy()
# Rimuovere righe con prodotto mancante (non recuperabile)
df_pulito = df_pulito.dropna(subset=['prodotto'])
# Riempire quantità mancante con la mediana
quantita_mediana = df_pulito['quantita'].median()
df_pulito['quantita'] = df_pulito['quantita'].fillna(quantita_mediana)
# Riempire prezzo mancante con la media del prodotto
prezzo_medio_x = df_pulito[df_pulito['prodotto'] == 'Libro X']['prezzo'].mean()
df_pulito.loc[df_pulito['prodotto'] == 'Libro X', 'prezzo'] = df_pulito.loc[df_pulito['prodotto'] == 'Libro X', 'prezzo'].fillna(prezzo_medio_x)
# Cliente mancante: assegnare "Cliente Sconosciuto"
df_pulito['cliente'] = df_pulito['cliente'].fillna('Cliente Sconosciuto')
# Correzione data errata
df_pulito['data_acquisto'] = pd.to_datetime(df_pulito['data_acquisto'], errors='coerce')
df_pulito = df_pulito.dropna(subset=['data_acquisto'])
print("=== DATASET PULITO ===")
print(df_pulito)
print(f"Shape dopo pulizia: {df_pulito.shape}")
print("\n")
# Calcolo fatturato dopo pulizia
df_pulito['fatturato'] = df_pulito['quantita'] * df_pulito['prezzo']
print("=== FATTURATO TOTALE ===")
print(f"Fatturato totale: €{df_pulito['fatturato'].sum():.2f}")
conn.close()
Output atteso:
=== DATASET ORIGINALE ===
id cliente prodotto quantita prezzo data_acquisto
0 1 Mario Rossi Libro X 2.0 15.0 2024-01-10
1 2 None Libro Y 1.0 20.0 2024-01-15
2 3 Luisa Verdi Libro Z NaN 18.5 2024-01-20
3 4 Giulia Neri Libro X 3.0 NaN 2024-02-05
4 5 Marco Bianchi None 2.0 12.0 2024-02-10
5 6 Anna Gialli Libro Y 1.0 20.0 data_errata
Shape originale: (6, 6)
=== VALORI MANCANTI ===
id 0
cliente 1
prodotto 1
quantita 1
prezzo 1
data_acquisto 0
dtype: int64
=== DATASET PULITO ===
id cliente prodotto quantita prezzo data_acquisto
0 1 Mario Rossi Libro X 2.0 15.0 2024-01-10
1 2 Cliente Sconosciuto Libro Y 1.0 20.0 2024-01-15
2 3 Luisa Verdi Libro Z 1.5 18.5 2024-01-20
3 4 Giulia Neri Libro X 3.0 15.0 2024-02-05
Shape dopo pulizia: (4, 6)
=== FATTURATO TOTALE ===
Fatturato totale: €122.75
Osservazioni:
💡 La gestione dei valori mancanti richiede strategie diverse a seconda del contesto. fillna() con statistiche appropriate è spesso meglio della semplice rimozione.
Esercizio 5: Analisi Avanzata con Query Complesse e Sottotabelle
Scenario:
Sei promosso ad analista senior. Devi creare un report completo con metriche multiple.
Soluzione:
import sqlite3
import pandas as pd
def crea_database_e_tabelle():
"""Crea il database e le tabelle in una funzione separata"""
conn = sqlite3.connect('analisi_avanzata.db')
cursor = conn.cursor()
# Schema complesso con gestione errori
try:
# Tabelle correlate
cursor.execute('''
CREATE TABLE IF NOT EXISTS prodotti (
prodotto_id INTEGER PRIMARY KEY,
nome TEXT,
categoria TEXT,
prezzo_listino REAL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite_avanzate (
vendita_id INTEGER PRIMARY KEY,
prodotto_id INTEGER,
data DATE,
quantita INTEGER,
prezzo_vendita REAL,
sconto_applicato REAL,
FOREIGN KEY (prodotto_id) REFERENCES prodotti(prodotto_id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS clienti (
cliente_id INTEGER PRIMARY KEY,
nome TEXT,
regione TEXT,
tipo_cliente TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite_clienti (
vendita_id INTEGER,
cliente_id INTEGER,
FOREIGN KEY (vendita_id) REFERENCES vendite_avanzate(vendita_id),
FOREIGN KEY (cliente_id) REFERENCES clienti(cliente_id)
)
''')
# Inserimento dati prodotti
prodotti = [
(1, 'Libro Fantasy A', 'Fantasy', 20.00),
(2, 'Libro Romanzo B', 'Romanzo', 15.00),
(3, 'Libro Giallo C', 'Giallo', 18.00),
(4, 'Libro Fantasy D', 'Fantasy', 22.00)
]
cursor.executemany('INSERT INTO prodotti VALUES (?, ?, ?, ?)', prodotti)
# Inserimento dati vendite
vendite = [
(1, 1, '2024-01-10', 2, 18.00, 2.00),
(2, 2, '2024-01-15', 1, 14.00, 1.00),
(3, 1, '2024-01-20', 3, 17.50, 2.50),
(4, 3, '2024-02-05', 2, 16.00, 2.00),
(5, 4, '2024-02-10', 1, 20.00, 2.00),
(6, 2, '2024-02-15', 2, 13.50, 1.50)
]
cursor.executemany('INSERT INTO vendite_avanzate VALUES (?, ?, ?, ?, ?, ?)', vendite)
# Inserimento dati clienti
clienti = [
(1, 'Mario Rossi', 'Nord', 'Premium'),
(2, 'Luisa Verdi', 'Centro', 'Standard'),
(3, 'Giulia Neri', 'Sud', 'Premium')
]
cursor.executemany('INSERT INTO clienti VALUES (?, ?, ?, ?)', clienti)
# Inserimento relazione vendite-clienti
vendite_clienti_data = [
(1, 1), (2, 2), (3, 1), (4, 3), (5, 2), (6, 1)
]
cursor.executemany('INSERT INTO vendite_clienti VALUES (?, ?)', vendite_clienti_data)
conn.commit()
print("✅ Database e tabelle creati con successo!")
except sqlite3.Error as e:
print(f"❌ Errore durante la creazione del database: {e}")
conn.rollback()
finally:
conn.close()
# SOLUZIONE 1: Creare prima il database, poi analizzare
crea_database_e_tabelle()
# SOLUZIONE 2: Usare context manager per gestire automaticamente le connessioni
def analisi_avanzata():
"""Esegue l'analisi avanzata usando context manager"""
with sqlite3.connect('analisi_avanzata.db') as conn:
# QUERY AVANZATA: Multiple metriche
query_avanzata = '''
SELECT
p.categoria,
COUNT(DISTINCT v.vendita_id) as numero_vendite,
SUM(v.quantita) as totale_quantita,
SUM(v.quantita * v.prezzo_vendita) as fatturato_totale,
AVG(v.prezzo_vendita) as prezzo_medio_vendita,
SUM(v.sconto_applicato * v.quantita) as sconto_totale,
COUNT(DISTINCT vc.cliente_id) as clienti_unici
FROM prodotti p
JOIN vendite_avanzate v ON p.prodotto_id = v.prodotto_id
JOIN vendite_clienti vc ON v.vendita_id = vc.vendita_id
GROUP BY p.categoria
'''
df_analisi_avanzata = pd.read_sql_query(query_avanzata, conn)
print("=== ANALISI AVANZATA PER CATEGORIA ===")
print(df_analisi_avanzata)
print("\n")
# Analisi aggiuntiva con Pandas
df_analisi_avanzata['fatturato_medio_per_vendita'] = (
df_analisi_avanzata['fatturato_totale'] / df_analisi_avanzata['numero_vendite']
)
df_analisi_avanzata['sconto_percentuale'] = (
df_analisi_avanzata['sconto_totale'] / df_analisi_avanzata['fatturato_totale'] * 100
)
print("=== METRICHE AGGIUNTE ===")
print(df_analisi_avanzata[['categoria', 'fatturato_medio_per_vendita', 'sconto_percentuale']])
print("\n")
return df_analisi_avanzata
# SOLUZIONE 3: Analisi dettagliata con query separate
def analisi_dettagliata():
"""Analisi più dettagliata con query multiple"""
with sqlite3.connect('analisi_avanzata.db') as conn:
# Query 1: Performance per prodotto
query_prodotti = '''
SELECT
p.nome,
p.categoria,
SUM(v.quantita) as vendite_totali,
SUM(v.quantita * v.prezzo_vendita) as fatturato,
AVG(v.prezzo_vendita) as prezzo_medio
FROM prodotti p
JOIN vendite_avanzate v ON p.prodotto_id = v.prodotto_id
GROUP BY p.nome, p.categoria
ORDER BY fatturato DESC
'''
df_prodotti = pd.read_sql_query(query_prodotti, conn)
print("=== PERFORMANCE PER PRODOTTO ===")
print(df_prodotti)
print("\n")
# Query 2: Clienti premium vs standard
query_clienti = '''
SELECT
c.tipo_cliente,
COUNT(DISTINCT c.cliente_id) as numero_clienti,
SUM(v.quantita) as prodotti_acquistati,
SUM(v.quantita * v.prezzo_vendita) as fatturato_totale
FROM clienti c
JOIN vendite_clienti vc ON c.cliente_id = vc.cliente_id
JOIN vendite_avanzate v ON vc.vendita_id = v.vendita_id
GROUP BY c.tipo_cliente
'''
df_clienti = pd.read_sql_query(query_clienti, conn)
print("=== ANALISI PER TIPO CLIENTE ===")
print(df_clienti)
# Esecuzione delle analisi
print("🎯 ESERCIZIO 5: ANALISI AVANZATA CON QUERY COMPLESSE")
print("=" * 60)
df_risultati = analisi_avanzata()
analisi_dettagliata()
# Analisi aggiuntiva con Pandas
print("=== ANALISI AVANZATA PANDAS ===")
print("📊 Statistiche descrittive del fatturato:")
print(f"Fatturato totale: €{df_risultati['fatturato_totale'].sum():.2f}")
print(f"Fatturato medio per categoria: €{df_risultati['fatturato_totale'].mean():.2f}")
print(f"Categoria con fatturato massimo: {df_risultati.loc[df_risultati['fatturato_totale'].idxmax(), 'categoria']}")
print(f"Sconto medio applicato: {df_risultati['sconto_percentuale'].mean():.2f}%")
Output atteso:
🎯 ESERCIZIO 5: ANALISI AVANZATA CON QUERY COMPLESSE
============================================================
=== ANALISI AVANZATA PER CATEGORIA ===
categoria numero_vendite totale_quantita fatturato_totale \
0 Fantasy 3 6 108.5
1 Giallo 1 2 32.0
2 Romanzo 2 3 41.0
prezzo_medio_vendita sconto_totale clienti_unici
0 18.50 13.5 2
1 16.00 4.0 1
2 13.75 4.0 2
=== METRICHE AGGIUNTE ===
categoria fatturato_medio_per_vendita sconto_percentuale
0 Fantasy 36.166667 12.442396
1 Giallo 32.000000 12.500000
2 Romanzo 20.500000 9.756098
=== PERFORMANCE PER PRODOTTO ===
nome categoria vendite_totali fatturato prezzo_medio
0 Libro Fantasy A Fantasy 5 88.5 17.75
1 Libro Romanzo B Romanzo 3 41.0 13.75
2 Libro Giallo C Giallo 2 32.0 16.00
3 Libro Fantasy D Fantasy 1 20.0 20.00
=== ANALISI PER TIPO CLIENTE ===
tipo_cliente numero_clienti prodotti_acquistati fatturato_totale
0 Premium 2 9 147.5
1 Standard 1 2 34.0
=== ANALISI AVANZATA PANDAS ===
📊 Statistiche descrittive del fatturato:
Fatturato totale: €181.50
Fatturato medio per categoria: €60.50
Categoria con fatturato massimo: Fantasy
Sconto medio applicato: 11.57%
Osservazioni:
💡 Le query SQL complesse possono calcolare multiple metriche in una singola esecuzione, mentre Pandas è ottimo per calcoli aggiuntivi e manipolazioni.
Esercizio 6: Sistema Completo di Reporting con Esportazione
Scenario:
Devi creare un sistema di reporting automatizzato che genera report in diversi formati.
Soluzione:
import sqlite3
import pandas as pd
import matplotlib.pyplot as plt
import os
# Creazione directory per report
os.makedirs('report', exist_ok=True)
conn = sqlite3.connect('reporting_system.db')
cursor = conn.cursor()
# Database per reporting
cursor.execute('''
CREATE TABLE IF NOT EXISTS vendite_reporting (
id INTEGER PRIMARY KEY,
regione TEXT,
prodotto TEXT,
trimestre TEXT,
vendite INTEGER,
budget INTEGER
)
''')
# Dati di vendite e budget
dati_reporting = [
(1, 'Nord', 'Libro A', 'Q1', 1200, 1000),
(2, 'Nord', 'Libro B', 'Q1', 800, 900),
(3, 'Centro', 'Libro A', 'Q1', 950, 800),
(4, 'Centro', 'Libro B', 'Q1', 700, 750),
(5, 'Sud', 'Libro A', 'Q1', 600, 500),
(6, 'Sud', 'Libro B', 'Q1', 400, 450),
(7, 'Nord', 'Libro A', 'Q2', 1300, 1100),
(8, 'Nord', 'Libro B', 'Q2', 850, 950),
(9, 'Centro', 'Libro A', 'Q2', 1000, 850),
(10, 'Centro', 'Libro B', 'Q2', 750, 800),
(11, 'Sud', 'Libro A', 'Q2', 650, 550),
(12, 'Sud', 'Libro B', 'Q2', 450, 500)]
cursor.executemany('INSERT INTO vendite_reporting VALUES (?, ?, ?, ?, ?, ?)', dati_reporting)
conn.commit()
# SISTEMA DI REPORTING COMPLETO
class ReportingSystem:
def __init__(self, db_connection):
self.conn = db_connection
self.reports = {}
def carica_dati(self):
"""Carica tutti i dati per l'analisi"""
query = "SELECT * FROM vendite_reporting"
self.df_completo = pd.read_sql_query(query, self.conn)
# Calcolo metriche aggiuntive
self.df_completo['performance'] = (
self.df_completo['vendite'] / self.df_completo['budget'] * 100
)
self.df_completo['scostamento'] = (
self.df_completo['vendite'] - self.df_completo['budget']
)
def genera_report_regioni(self):
"""Report performance per regione"""
report_regioni = self.df_completo.groupby('regione').agg({
'vendite': 'sum',
'budget': 'sum',
'performance': 'mean'
}).round(2)
report_regioni['performance_totale'] = (
report_regioni['vendite'] / report_regioni['budget'] * 100
).round(2)
self.reports['regioni'] = report_regioni
return report_regioni
def genera_report_prodotti(self):
"""Report performance per prodotto"""
report_prodotti = self.df_completo.groupby('prodotto').agg({
'vendite': 'sum',
'budget': 'sum',
'performance': 'mean'
}).round(2)
self.reports['prodotti'] = report_prodotti
return report_prodotti
def analisi_trimestrale(self):
"""Analisi dettagliata trimestrale"""
pivot_vendite = pd.pivot_table(
self.df_completo,
values='vendite',
index='prodotto',
columns='trimestre',
aggfunc='sum'
)
pivot_budget = pd.pivot_table(
self.df_completo,
values='budget',
index='prodotto',
columns='trimestre',
aggfunc='sum'
)
self.reports['pivot_vendite'] = pivot_vendite
self.reports['pivot_budget'] = pivot_budget
return pivot_vendite, pivot_budget
def esporta_report(self):
"""Esporta tutti i report in diversi formati"""
# CSV
for nome, report in self.reports.items():
filename = f'report/{nome}_report.csv'
report.to_csv(filename)
print(f"Report {nome} esportato in: {filename}")
# Excel con multiple sheets
with pd.ExcelWriter('report/report_completo.xlsx') as writer:
for nome, report in self.reports.items():
report.to_excel(writer, sheet_name=nome[:31]) # Limite caratteri Excel
print("Report Excel completo creato: report/report_completo.xlsx")
def crea_grafico_performance(self):
"""Crea grafico delle performance"""
plt.figure(figsize=(12, 6))
report_regioni = self.reports['regioni']
regioni = report_regioni.index
performance = report_regioni['performance_totale']
plt.bar(regioni, performance, color=['green' if x >= 100 else 'red' for x in performance])
plt.axhline(y=100, color='blue', linestyle='--', label='Target 100%')
plt.title('Performance per Regione (%)')
plt.ylabel('Performance %')
plt.legend()
plt.savefig('report/performance_regioni.png', dpi=300, bbox_inches='tight')
plt.close()
print("Grafico creato: report/performance_regioni.png")
# ESECUZIONE SISTEMA
sistema = ReportingSystem(conn)
sistema.carica_dati()
print("=== REPORT REGIONI ===")
print(sistema.genera_report_regioni())
print("\n")
print("=== REPORT PRODOTTI ===")
print(sistema.genera_report_prodotti())
print("\n")
print("=== ANALISI TRIMESTRALE ===")
vendite_trim, budget_trim = sistema.analisi_trimestrale()
print("Vendite per trimestre:")
print(vendite_trim)
print("\nBudget per trimestre:")
print(budget_trim)
# Esportazione
sistema.esporta_report()
sistema.crea_grafico_performance()
conn.close()
print("\n=== SISTEMA DI REPORTING COMPLETATO ===")
print("Tutti i report sono stati generati nella cartella 'report/'")
Output atteso:
=== REPORT REGIONI ===
vendite budget performance performance_totale
regione
Centro 3400 3200 112.92 106.25
Nord 4250 4000 110.12 106.25
Sud 2100 2000 111.67 105.00
=== REPORT PRODOTTI ===
vendite budget performance
prodotto
Libro A 4100 3750 113.61
Libro B 2800 3000 108.61
=== ANALISI TRIMESTRALE ===
Vendite per trimestre:
trimestre Q1 Q2
prodotto
Libro A 2750 2950
Libro B 1900 1800
Budget per trimestre:
trimestre Q1 Q2
prodotto
Libro A 2300 2450
Libro B 2100 2250
...
Report regioni esportato in: report/regioni_report.csv
Report prodotti esportato in: report/prodotti_report.csv
Report pivot_vendite esportato in: report/pivot_vendite_report.csv
Report pivot_budget esportato in: report/pivot_budget_report.csv
Report Excel completo creato: report/report_completo.xlsx
Grafico creato: report/performance_regioni.png
=== SISTEMA DI REPORTING COMPLETATO ===
Tutti i report sono stati generati nella cartella 'report/'
Osservazioni:
💡 Un sistema di reporting completo combina SQL per l’estrazione dati, Pandas per l’analisi e manipolazione, e varie librerie per l’esportazione e visualizzazione.
Articoli di approfondimento
- 🔴 Guida pratica a SQLite3 in Python: da zero a pandas — esempi e best practice
- 🔴 Pandas per principianti: come analizzare un dataset in 10 minuti
- 🔴 Pandas (Python) — Tutorial completo per l’analisi e la manipolazione dei dati
- 🔴 Python: la libreria pandas — le operazioni sui DataFrame
- 🔴 Python: la libreria pandas — le operazioni sulle Series
- 🔴 Pulizia dati con Python: guida pratica — da dataset sporco ad analisi affidabile con pandas
- 🔴 Dataset di qualità: la guida completa — dalla raccolta dati alla pulizia per la tua AI
Commento Applicativo degli Esercizi
Esercizio 1: Analisi di un E-commerce Base
- Questo è il “pane quotidiano”. È la prima richiesta che riceve un analista: “Quali sono i prodotti più venduti?”.
- Peculiarità (Perché è interessante): Insegna la pipeline fondamentale: Connect -> Query -> Load -> Analyze. L’uso di
pd.read_sql_queryè la funzione cardine che unisce i due mondi. Ilgroupby().sum()è l’operazione di aggregazione più comune. È l’equivalente del “Hello, World!” per la data analysis.
Esercizio 2: Gestione Inventario con JOIN
- I dati non vivono mai in un’unica tabella. Per calcolare il fatturato (Vendite * Prezzo), è necessario unire la tabella delle vendite con quella dei libri. Per sapere chi ha generato quel fatturato, serve la tabella degli editori.
- Peculiarità: Questo esercizio dimostra perché è più efficiente eseguire il
JOINin SQL (come fatto nellaquery_complessa) piuttosto che caricare tre tabelle separate in Pandas e poi usarepd.merge(). Si delega il lavoro pesante di unione al database, che è ottimizzato per farlo.
Esercizio 3: Analisi Temporale delle Vendite
- Analizzare i totali non basta. Bisogna capire quando avvengono le vendite. C’è stagionalità? Stiamo crescendo mese su mese?
- Peculiarità: La gestione delle date è cruciale. L’esercizio mostra il modo “Pandas-iano” corretto: (1) Convertire la colonna in un oggetto
datetime64[ns]e (2) Usare l’accessor.dt(in questo caso.dt.to_period('M')) per estrarre e raggruppare per periodi. È molto più pulito e potente che manipolare stringhe.
Esercizio 4: Pulizia Dati e Gestione Valori Mancanti
- Questo è l’esercizio più importante e realistico di tutti. I dati non sono mai puliti. I clienti dimenticano di inserire campi, i sistemi falliscono, le date sono corrotte.
- Peculiarità: È fantastico perché non si limita a un banale
dropna(). Mostra strategie di pulizia contestuali:- Riempire con la mediana:
fillna(quantita_mediana)è robusto in caso di outlier (se un cliente avesse comprato 1000 copie, la media sarebbe falsata, la mediana no). - Riempire con una media di gruppo: Il prezzo del “Libro X” viene riempito con la media solo degli altri “Libro X”. Questa è una tecnica avanzata e corretta.
- Gestire errori di tipo:
pd.to_datetime(errors='coerce')è il modo migliore per convertire le date, trasformando automaticamente i formati errati (‘data_errata’) inNaT(Not a Time), che possono poi essere filtrati.
- Riempire con la mediana:
Esercizio 5: Analisi Avanzata con Query Complesse
- Questo simula la creazione di un set di KPI (Key Performance Indicators) per una dashboard. L’obiettivo è avere una vista aggregata (per categoria, per tipo cliente) che riassuma la salute del business.
- Mostra la vera sinergia. La query SQL fa il “lavoro sporco” di aggregazione pesante (
COUNT(DISTINCT),AVG,SUMmultipli) su più tabelle. Poi, Pandas viene usato per il “lavoro di fino”: calcolare metriche derivate (comesconto_percentuale) che sarebbero state molto complesse da calcolare direttamente in SQL.
Esercizio 6: Sistema Completo di Reporting con Esportazione
- Il lavoro di un analista non finisce con un
print(). I risultati devono essere comunicati in modo chiaro, condivisibile e ripetibile. - Questo è un mini-progetto “capstone”.
- OOP (Object-Oriented): Incapsulare la logica in una classe (
ReportingSystem) rende il processo riutilizzabile. Domani, basterà cambiare il database e chiamare.carica_dati(). - Pivot Table: Mostra
pd.pivot_table, uno strumento potente per confrontare metriche (es. Vendite vs Budget per Trimestre). - Output Multi-formato: Esporta sia in CSV che in un singolo file Excel con fogli multipli, una richiesta aziendale incredibilmente comune.
- Visualizzazione: Chiude il cerchio creando un grafico (
matplotlib) che comunica il risultato (performance vs target) in modo visivo e immediato.
- OOP (Object-Oriented): Incapsulare la logica in una classe (
Riepilogo Apprendimento
Concetti Chiave Appresi:
- Pandas + SQLite3 Integration: Come combinare la potenza di SQL per query complesse con la flessibilità di Pandas per l’analisi
- Data Cleaning: Strategie per gestire valori mancanti e dati inconsistenti
- GroupBy Operations: Aggregazione dati per categorie multiple
- Time Series Analysis: Manipolazione e analisi di dati temporali
- Multi-Table JOINs: Combinazione di dati da tabelle correlate
- Advanced Reporting: Creazione di sistemi completi di analisi e reporting
Competenze Sviluppate:
- Pensiero analitico per problemi di business reali
- Gestione di dataset complessi e correlati
- Automazione di processi di reporting
- Creazione di metriche personalizzate
- Esportazione risultati in formati diversi
Questi esercizi coprono le competenze essenziali richieste per posizioni di data analysis junior e forniscono una solida base per affrontare problemi reali nel mondo del lavoro.





