NBA stats đ
Introduction
Vous allez travailler sur des données de la nba depuis les années 2000.
Les donnĂ©es sont brutes i.e. elles ont Ă©tĂ© rĂ©cupĂ©rĂ©e via lâapi stats nba et elles nâont quasiment pas Ă©tĂ© retouchĂ©es.
Le modĂšle de donnĂ©es nâest pas fourni ! Ce sera Ă vous dâexplorer vous mĂȘme la base de donnĂ©es.
Dans votre vie professionnelle, dans 95 % des cas vous allez hĂ©riter de missions oĂč la documentation est obsolĂšte voire inexistante.
Il faut donc apprendre Ă vous dĂ©brouiller dĂšs maintenant. Et au passage, vous convaincre que faire et maintenir la doc ça aide et câest bien !
1 Importer la base de données
Aujourdâhui, pas de script sql pour crĂ©er les tables et importer les donnĂ©es, vous allez utiliser un dump.
Un dump de base de donnĂ©es est un fichier qui contient une copie complĂšte dâune base de donnĂ©es.
Il sert Ă :
- Sauvegarder une base de données
- Transférer une base vers un autre serveur
- Reconstituer la base Ă lâidentique (tables, donnĂ©es, index, etc.)
En bref : câest une photographie de la base de donnĂ©es Ă un instant donnĂ©, utilisable pour la recrĂ©er ailleurs.
Le fichier nba.dump est disponible sur le stockage S3 de lâenseignant.
Pour rĂ©cupĂ©rer ce fichier et lâutiliser pour votre base de donnĂ©es :
-
- â ïž Si vous ĂȘtes sur le datalab du GENES
- âȘïž Avant de lancer le script
5_import_for_students - âȘïž Dans le FILE_PATH, remplacez ludo2ne par ldeneuville-ensai
Le script 5_import_for_students.py :
- Se connecte au S3 de lâenseignant pour rĂ©cupĂ©rer le fichier nba.dump
- Vous remarquerez que le fichier est dans un dossier nommé diffusion
- Ce dossier est accessible en lecture aux autres utilisateurs
- Utilise les variables dâenvionnement pour se connecter Ă votre base de donnĂ©es
- Celles que vous avez renseigné dans le fichier .env
- Utilise lâutilitaire pg_restore pour crĂ©er la base de donnĂ©es Ă partir du dump
2 Rendez-vous en base inconnue
Je nâai pas rĂ©ussi Ă inviter Laury T. pour vous accompagner dans cette aventure mais voici quelques requĂȘtes utiles pour explorer une base de donnĂ©es PostgreSQL.
-- Liste des tables du schema
SELECT table_name
FROM information_schema.tables
WHERE table_schema = '<schema_name>'
AND table_type = 'BASE TABLE'
ORDER BY table_name;
-- Colonnes d une table
SELECT column_name,
data_type,
is_nullable,
column_default
FROM information_schema.columns
WHERE table_schema = '<...>'
AND table_name = '<...>'
ORDER BY ordinal_position;
-- Colonnes commencant ou terminant par id
SELECT table_name,
column_name,
data_type
FROM information_schema.columns
WHERE table_schema = '<...>'
AND (column_name ILIKE 'id%' OR column_name ILIKE '%id')
ORDER BY table_name, column_name;
-- Generer les CREATE TABLE basiques
SELECT 'CREATE TABLE ' || table_schema || '.' || table_name || ' ( ' || E'\n' ||
STRING_AGG(
' ' || column_name || ' ' || UPPER(data_type),
',' || E'\n'
ORDER BY ordinal_position
)
|| E'\n);' AS create_table_script
FROM information_schema.columns
WHERE table_schema = '<...>'
GROUP BY table_schema, table_name
ORDER BY table_name;Liste des colonnes des tables dâun schĂ©ma
WITH
params AS (
SELECT
'<...>' AS schemaname
),
cols AS (
SELECT
c.table_schema,
c.table_name,
c.column_name,
c.ordinal_position,
c.is_nullable,
c.udt_name,
c.character_maximum_length,
c.column_default
FROM information_schema.columns c
JOIN params p ON c.table_schema = p.schemaname
),
pks AS (
SELECT DISTINCT
kcu.table_schema,
kcu.table_name,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN params p ON tc.table_schema = p.schemaname
WHERE tc.constraint_type = 'PRIMARY KEY'
),
uniques AS (
SELECT DISTINCT
kcu.table_schema,
kcu.table_name,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN params p ON tc.table_schema = p.schemaname
WHERE tc.constraint_type = 'UNIQUE'
),
fks AS (
SELECT DISTINCT ON (kcu.table_schema, kcu.table_name, kcu.column_name)
kcu.table_schema,
kcu.table_name,
kcu.column_name,
ccu.table_name AS ref_table,
ccu.column_name AS ref_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON tc.constraint_name = ccu.constraint_name
JOIN params p ON tc.table_schema = p.schemaname
WHERE tc.constraint_type = 'FOREIGN KEY'
ORDER BY kcu.table_schema, kcu.table_name, kcu.column_name
)
SELECT
c.table_name AS "Table",
c.column_name AS "Column",
c.udt_name || COALESCE('(' || c.character_maximum_length::text || ')', '') AS "Type",
CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END AS "PK",
CASE WHEN fk.ref_table IS NOT NULL THEN fk.ref_table || '.' || fk.ref_column END AS "FK",
CASE WHEN pk.column_name IS NOT NULL THEN true ELSE false END OR CASE WHEN u.column_name IS NOT NULL THEN true ELSE false END AS "Unique",
CASE WHEN c.is_nullable ='YES' THEN true ELSE false END AS "Nullable",
c.column_default AS "Default value"
FROM cols c
LEFT JOIN pks pk
ON pk.table_schema = c.table_schema
AND pk.table_name = c.table_name
AND pk.column_name = c.column_name
LEFT JOIN uniques u
ON u.table_schema = c.table_schema
AND u.table_name = c.table_name
AND u.column_name = c.column_name
LEFT JOIN fks fk
ON fk.table_schema = c.table_schema
AND fk.table_name = c.table_name
AND fk.column_name = c.column_name
ORDER BY
c.table_name,
c.ordinal_position;Une requĂȘte quasi complĂšte pour crĂ©er les tables dâun schĂ©ma :
WITH
params AS (
SELECT '<...>' AS schema_name
),
tables AS (
SELECT table_name
FROM information_schema.tables t
JOIN params p ON t.table_schema = p.schema_name
WHERE t.table_type = 'BASE TABLE'
),
cols AS (
SELECT
c.table_schema,
c.table_name,
c.ordinal_position,
c.column_name,
c.udt_name,
c.character_maximum_length,
c.is_nullable,
c.column_default
FROM information_schema.columns c
JOIN params p ON c.table_schema = p.schema_name
JOIN tables t ON c.table_name = t.table_name
),
pks AS (
SELECT DISTINCT
tc.table_schema,
tc.table_name,
kcu.column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN params p ON tc.table_schema = p.schema_name
WHERE tc.constraint_type = 'PRIMARY KEY'
),
uniques AS (
SELECT DISTINCT
tc.table_schema,
tc.table_name,
tc.constraint_name,
STRING_AGG(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) AS cols
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN params p ON tc.table_schema = p.schema_name
WHERE tc.constraint_type = 'UNIQUE'
GROUP BY tc.table_schema, tc.table_name, tc.constraint_name
),
fks AS (
SELECT DISTINCT
tc.table_schema,
tc.table_name,
tc.constraint_name,
kcu.column_name,
ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.referential_constraints rc
ON tc.constraint_name = rc.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON rc.constraint_name = ccu.constraint_name
JOIN params p ON tc.table_schema = p.schema_name
WHERE tc.constraint_type = 'FOREIGN KEY'
)
SELECT
'CREATE TABLE ' || c.table_schema || '.' || c.table_name || ' (' || E'\n' ||
STRING_AGG(
' ' || c.column_name || ' ' || c.udt_name ||
CASE WHEN c.character_maximum_length IS NOT NULL
THEN '(' || c.character_maximum_length || ')' ELSE '' END ||
CASE WHEN c.column_default IS NOT NULL
THEN ' DEFAULT ' || c.column_default ELSE '' END ||
CASE WHEN c.is_nullable = 'NO'
THEN ' NOT NULL' ELSE '' END,
',' || E'\n'
ORDER BY c.ordinal_position
)
-- PRIMARY KEY
||
COALESCE((
SELECT ',' || E'\n PRIMARY KEY (' ||
STRING_AGG(DISTINCT column_name, ', ') ||
')'
FROM pks pk
WHERE pk.table_name = c.table_name
), '')
-- UNIQUE constraints
||
COALESCE((
SELECT STRING_AGG(
',' || E'\n CONSTRAINT ' || constraint_name ||
' UNIQUE (' || cols || ')'
, '')
FROM uniques u
WHERE u.table_name = c.table_name
), '')
-- FOREIGN KEYS
||
COALESCE((
SELECT STRING_AGG(
',' || E'\n CONSTRAINT ' || constraint_name ||
' FOREIGN KEY (' || column_name || ')' ||
' REFERENCES ' || foreign_table_schema || '.' || foreign_table_name ||
' (' || foreign_column_name || ')'
, '')
FROM fks fk
WHERE fk.table_name = c.table_name
), '')
|| E'\n);\n' AS create_table_script
FROM cols c
GROUP BY c.table_schema, c.table_name
ORDER BY c.table_name;
);| Colonne | Signification |
|---|---|
| matchup | Description du match : ex. LAL @ BOS (Ă lâextĂ©rieur) ou LAL vs BOS (Ă domicile). |
| wl | RĂ©sultat pour lâĂ©quipe : W (win), L (loss). |
| min | Minutes totales jouĂ©es par lâĂ©quipe (toujours 240 sauf prolongation). |
| pts | Total des points de lâĂ©quipe. |
| fgm | Field goals made â tirs rĂ©ussis (2 pts + 3 pts). |
| fga | Field goals attempted â tirs tentĂ©s. |
| fg_pct | Taux de réussite : fgm / fga. |
| fg3m | 3-points réussis. |
| fg3a | 3-points tentés. |
| fg3_pct | Pourcentage Ă 3 points. |
| ftm | Free throws made â lancers francs rĂ©ussis. |
| fta | Free throws attempted â lancers francs tentĂ©s. |
| ft_pct | Pourcentage aux lancers. |
| oreb | Rebonds offensifs. |
| dreb | Rebonds défensifs. |
| reb | Rebonds totaux (oreb + dreb). |
| ast | Passes décisives. |
| stl | Interceptions. |
| blk | Contres. |
| tov | Ballons perdus (turnovers). |
| pf | Fautes personnelles. |
Si un modÚle de données est nécessaire pour vous, vous pouvez par exemple :
- Générer les CREATE TABLE
- Demander à une IAgen de vous générer le modÚle
- Par exemple au format utilsé par ce site : https://dbdiagram.io/
3 Explorons avec les Spurs
Vous allez avoir parfois des incompatibilités de types.
Vous pouvez utiliser par exemple <colname>::int ou <colname>::str pour caster.
-
- classez ces matchs par date
-
- Choisissez des exemples de game_id et vérifiez
- Ou mieux Ă©crivez une requĂȘte qui vĂ©rifie lâhypothĂšse pour tous les game_id
Ce choix de conception a des avantages et des inconvénients. Nous y reviendrons par la suite lorsque nous afficherons les scores de chaque match.
Regardons maintenant les stats des joueurs.
4 Afficher les matchs
Dans toute cette partie nous nous interesserons Ă la saison 2020-2021.
Interessons-nous la saison réguliÚre.
-
- ajoutez le nombre de matchs joués
-
- classez par nombre de victoires décroissant
Passons maintenant aux Playoffs.
Une derniĂšre question pour la route :
Tout simplement :
- Trouvez le dernier match des playoffs i.e. celui qui a le plus grand id
- DĂ©duisez lâĂ©quipe ayant gagnĂ© le dernier match et la finaliste
- Enfin listez tous les matchs de la finales pour avoir le score
WITH
last_playoff_games AS (
SELECT season_id, MAX(game_id) AS last_game_id
FROM nba.game
WHERE season_type = 'Playoffs'
GROUP BY season_id
),
final_teams AS (
SELECT g.season_id,
g.team_id AS team_id_win,
g2.team_id AS team_id_los
FROM nba.game g
JOIN last_playoff_games lpg ON g.season_id = lpg.season_id AND g.game_id = lpg.last_game_id
JOIN nba.game g2 ON g2.game_id = g.game_id AND g2.team_id <> g.team_id
WHERE g.wl = 'W'
)
SELECT gwin.season_id,
twin.full_name AS equipe,
COUNT(*) FILTER (WHERE gwin.wl = 'W') || ' - ' || COUNT(*) FILTER (WHERE gwin.wl = 'L') AS score,
tlos.full_name AS adversaire
FROM nba.game gwin
JOIN final_teams ft ON ft.team_id_win = gwin.team_id AND ft.season_id = gwin.season_id
JOIN nba.team twin ON twin.id = gwin.team_id
JOIN nba.game glos ON glos.game_id = gwin.game_id AND glos.team_id = ft.team_id_los
JOIN nba.team tlos ON tlos.id = glos.team_id
WHERE gwin.season_type = 'Playoffs'
GROUP BY gwin.season_id, twin.full_name, tlos.full_name
ORDER BY gwin.season_id DESC;5 Triple zéro
Lors dâun match, les principales statistiques utilisĂ©es pour mesurer les performances des joueurs sont : les points, les rebonds, les passes dĂ©cisives, les interceptions et les contres.
Un joueur réalise alors un :
- double-double : sâil atteint au moins 10 dans deux de ces statistiques
- triple-double : au moins 10 dans trois de ces statistiques (grosse perf)
- quadruple-double : au moins 10 dans quatre de ces statistiques (trĂšs rare)
Il existe Ă©galement dâautres performances trĂšs rares :
- double triple-double : sâil atteint au moins 20 dans trois de ces statistiques
- Five-by-five : au moins 5 dans les cinq catégories
Vous aussi en avez assez dâentendre toujours les mĂȘmes noms rĂ©aliser ce genre de performance : Wemby, Curry, Gilgeous-Alexander, Doncic, JokicâŠ
Lâheure de la revanche a sonnĂ©. Mettons aujourdâhui en lumiĂšre les vrais champions, ceux qui rĂ©alisent des 0 dans ces stats, font des fautes, perdent des balles.
- Commencez par retirer les lignes ou la colonne minutes est vide
- Puis utilisez
CAST(SPLIT_PART(minutes, ':', 1) AS integer)
-
- quadruple zéro : 0 points et 3 autres stats à 0
- Le
CASE WHENest votre ami ici
Quelques autres titres à décerner (toujours avec au moins 10min de jeu):
-
- i.e. qui nâa marquĂ© aucun point, et a tentĂ© le plus grand nombre de tirs, lancers francs inclus
La colonne plusminuspoints est la diffĂ©rence entre le nombre de points marquĂ©s par lâĂ©quipe et le nombre de points encaissĂ©s lorsque le joueur est sur le terrain.
Cela donne une idĂ©e de lâinfluence et lâimpact du joueur lorsquâil est sur le terrain.
Ils ont essayĂ© de faire perdre leur Ă©quipe mais ils nâont pas rĂ©ussi :
6 Classement par saison
-
- Triez par pourcentage de victoires
- En cas dâĂ©galitĂ©, vous pouvez si vous le souhaitez trouver les autres critĂšres de dĂ©paratage mais cela complique grandement la requĂȘte
-
- Procédez par étapes
Pour chaque saison réguliÚre donnez le match :
ArrĂȘtez vos services
Câest la fin du TP, vous pouvez maintenant sauvegarder votre travail et libĂ©rer les ressources rĂ©servĂ©es :