Imputació¶
Exercici imputation-annex.pdf · PDF
El teu professor està a punt de calcular la mitjana de la classe. Quatre alumnes, tres notes al quadern: 6, 8 i 10. La teva cel·la és buida. Què hi ha d'anar?
Un zero? Aleshores la mitjana baixa de 8 a 6, i acabes de suspendre un examen que et vas perdre perquè estaves malalt. La mitjana de la classe? Aleshores reps una nota per un treball que ningú no ha vist. Res? Aleshores la mitjana es calcula, en silenci, sense tu. Tres opcions, tres mitjanes diferents, i cap d'elles no és "evidentment la bona". Cada cel·la buida d'una taula fa aquesta mateixa pregunta. Aquesta pàgina va de respondre-la honestament.
Què aprendràs
- Comptar els valors absents d'una columna i expressar-los com a percentatge de les files
- Predir què fan
AVG,COUNTi+quan es troben unNULL - Substituir un
NULLambCOALESCEi mantenir una columna indicadora que recorda quins valors s'han omplert - Tornar a convertir un valor fals en un
NULLambNULLIF - Triar entre esborrar la fila, una constant, un valor central i "deixar-ho buit", i defensar l'elecció en una frase
- Evitar la trampa: per què omplir el 89% d'una columna amb la seva mitjana destrueix la columna
Un buit no és un zero¶
Quatre lliçons de neteja, i aquestes cel·les de games encara són buides:
| Columna | Cel·les buides | Proporció de les files |
|---|---|---|
metascore |
23,838 | 89.3% |
price |
3,095 | 11.6% |
developer |
152 | 0.6% |
publisher |
120 | 0.4% |
game |
3 | 0.01% |
Una cel·la buida no es pot netejar. No hi ha res a dins per reparar. Només pots decidir què en fas.
Imputació és decidir quin valor, si n'hi ha cap, ocupa el lloc d'un valor absent, i deixar-ho escrit.
L'analogia de tota la pàgina és el quadern del professor de l'inici: cada decisió que prens aquí és una fila seva.
| Opció | Al quadern | És justa quan... |
|---|---|---|
| Esborrar la fila | Treure l'alumne de la llista | La fila no serveix de res sense aquest valor, i n'hi ha molt poques |
| Una constant | Escriure un 0 | Tu saps què vol dir el buit: el treball no es va lliurar |
| Un valor central | Escriure la mitjana de la classe, o la mediana | Hi ha pocs buits, i els qui els tenen no tenen res d'especial |
| Deixar-ho buit i marcar-ho | No escriure res, afegir una nota "no avaluat" | No ho saps, o la major part de la columna és buida |
Un zero és una nota. Un buit és una pregunta: per què és buit? La resposta a aquesta pregunta, no l'SQL, decideix l'opció.
flowchart TB
Q1{"Saps què vol dir<br>el buit?"}
Q1 -- "Sí" --> C["<b>Una constant</b><br>escriu què vol dir<br>i afegeix un indicador"]
Q1 -- "No" --> Q2{"Els buits són pocs<br>i repartits a l'atzar?"}
Q2 -- "Sí" --> M["<b>Un valor central</b><br>mitjana o mediana<br>i afegeix un indicador"]
Q2 -- "No: molts, o un grup especial" --> L["<b>Deixa'l NULL</b><br>i afegeix un indicador"]
Q1 -- "La fila no serveix sense ell" --> D["<b>Esborra la fila</b><br>només si són molt pocs"]
Què fa NULL en un càlcul¶
Abans d'omplir res, has de saber què fa la base de dades amb una cel·la buida quan la deixes tal com és. Fa tres coses diferents, i cap no et demana permís.
| Operació | Es troba un NULL |
Exemple del quadern (6, 8, buit, 10) |
|---|---|---|
COUNT(*) |
Compta la fila igualment | 4 alumnes |
COUNT(column) |
El salta | 3 notes |
AVG(column), SUM(column) |
El salten | Mitjana 8: divideix 24 entre 3, no entre 4 |
column + 1, column < 5 |
El resultat és NULL |
buit + 1 = buit |
Mira-ho a la taula real. La mateixa columna, dues mitjanes:
SELECT ROUND(AVG(price)::numeric, 2) AS avg_ignoring_null,
ROUND(AVG(COALESCE(price, 0))::numeric, 2) AS avg_free_as_zero
FROM games;
| avg_ignoring_null | avg_free_as_zero |
|---|---|
| 8.95 | 7.91 |
Quina és "el preu mitjà d'un joc a Steam"? Depèn de què vol dir el buit. AVG ha saltat les files buides en silenci: 8.95 és una suma dividida entre 23,593 jocs, no entre 26,688. Ningú no li ho ha dit.
Compte: AVG no compta els buits com a 0
Aquesta és la suposició equivocada més comuna. AVG salta un NULL; no el tracta com un zero. Justament per això les dues mitjanes són diferents. Sempre que publiquis una mitjana, digues sobre quantes files s'ha calculat.
COALESCE: el valor i la nota al marge¶
COALESCE(a, b, ...)retorna el primer dels seus arguments que no ésNULL. Llegeix-ho en veu alta com "a, i si és buit,b".
SELECT game,
COALESCE(price, 0)::numeric(8,2) AS price, -- the value we filled in
(price IS NULL) AS is_free -- the note in the margin
FROM games
WHERE game IN ('Dota 2', 'Half-Life 2');
| game | price | is_free |
|---|---|---|
| Dota 2 | 0.00 | true |
| Half-Life 2 | 9.99 | false |
Una columna indicadora (flag) és una columna addicional, certa o falsa, que registra alguna cosa sobre la fila. Aquí: "aquest valor era buit a les dades originals".
Les dues sempre viatgen en parella: el valor omplert i un indicador que recorda que era buit. Quan el NULL s'ha sobreescrit, ningú no pot distingir un 0 real d'un d'inventat. L'indicador és la nota que el professor escriu al marge del quadern.
Compte: COALESCE no ha arreglat la taula
Com CAST, canvia el resultat d'aquesta consulta. games no s'ha tocat. L'omplert només es fa permanent quan la consulta neta es converteix en la taula games_clean, a la lliçó següent.
El reemplaçament ha de tenir el mateix tipus que la columna. COALESCE(developer, 'Unknown') funciona: text per a text. COALESCE(developer, 0) s'atura amb COALESCE types text and integer cannot be matched. Un error sorollós, del bo.
price: una constant que pots defensar¶
La lliçó de categorització va etiquetar els jocs sense preu com a Free. Era una suposició. Ara comprova si en teníem dret. Busca proves abans d'omplir res:
SELECT COUNT(*) FROM games WHERE price = 0; -- 0
SELECT MIN(price) FROM games; -- 0.49
SELECT game, owners FROM games
WHERE price IS NULL ORDER BY number LIMIT 4;
| game | owners |
|---|---|
| Team Fortress 2 | 50,000,000 .. 100,000,000 |
| Dota 2 | 100,000,000 .. 200,000,000 |
| Divinity: Original Sin (Classic) | 1,000,000 .. 2,000,000 |
| BioShock 2 | 2,000,000 .. 5,000,000 |
Tres proves:
| Prova | Què et diu |
|---|---|
| Cap joc no costa exactament 0 | Els jocs gratuïts han d'estar en un altre lloc |
| El preu més baix registrat és 0.49 | El zero no s'escriu mai com a preu |
| Els buits comencen amb Team Fortress 2 i Dota 2 | Jocs gratuïts famosos tenen el preu buit |
El coneixement del domini és el que saps del món real que hi ha darrere de les dades: com funciona Steam, què és un joc free-to-play. Cap consulta no te'l pot donar.
El coneixement del domini diu que un joc sense preu a Steam és gratuït (free to play), i les dades hi estan d'acord. Decisió: una constant, 0, més l'indicador is_free.
La nota a peu de pàgina honesta
Torna a llegir la llista. BioShock 2 tampoc no té preu, i Ori and the Blind Forest, una mica més avall, tampoc. Cap dels dos no va ser mai un joc gratuït. Això trenca la regla? La debilita; no la trenca.
La imputació és una suposició escrita en SQL. Un bon analista escriu la suposició al costat del número: "preu 0 vol dir que no hi ha preu llistat, gairebé sempre és free to play". Aquesta frase pertany a l'informe.
Així doncs, quina mitjana publiques? Les dues són respostes correctes a preguntes diferents:
| Pregunta | Mitjana | Calculada sobre |
|---|---|---|
| "Quant costa un joc a Steam?" | 7.91 | Tots els 26,688 jocs, els gratuïts com a 0 |
| "Quant costa un joc de pagament?" | 8.95 | Els 23,593 jocs amb preu |
metascore: per què no s'omple¶
Ara, el gran forat: falta el 89.3% de les puntuacions. La temptació és un valor central.
La mitjana és la suma dels valors dividida pel nombre de valors. La mediana és el valor del mig quan els ordenes. Les dues descriuen un valor "típic"; les estudiaràs bé a Estadística exploratòria.
La mitjana del metascore és 71.9 i la mediana és 73. Així que omple els buits amb 72, i compta:
SELECT COUNT(*) FROM games WHERE metascore = 72; -- 114
SELECT COUNT(*) FROM games WHERE COALESCE(metascore, 72) = 72; -- 23952
114 jocs van treure realment un 72. Després d'omplir, 23,952 jocs "han tret" un 72. Mira què li fa això a la columna:
La part perillosa: la mitjana gairebé no es mou. Era 71.9 i passa a ser 72.0. La mitjana amaga el desastre. Tota la resta de la columna (com de dispersades són les puntuacions, quins jocs són bons, quins dolents) s'ha aplanat en un únic pic.
Compte: omplir amb la mitjana no és neutral
Conserva la mitjana i destrueix tota la resta. Nou de cada deu valors de la columna serien números inventats per tu. Un gràfic d'aquesta columna ja no mostra puntuacions de crítica. Mostra la teva imputació.
Qui falta?¶
Hi ha una segona raó, i és la més forta. Comprova quins jocs no tenen puntuació. Primer els jocs petits:
SELECT (owners LIKE '0%') AS under_20k_owners,
COUNT(*) AS games,
COUNT(metascore) AS with_score,
ROUND(100.0 * COUNT(metascore) / COUNT(*), 1) AS pct_with_score
FROM games GROUP BY 1;
| under_20k_owners | games | with_score | pct_with_score |
|---|---|---|---|
| false | 8765 | 2363 | 27.0 |
| true | 17923 | 487 | 2.7 |
Entre els jocs amb 20,000 propietaris o més, el 27.0% té puntuació. Entre els més petits, el 2.7%. Deu vegades menys. Els crítics ressenyen els jocs de què la gent ha sentit parlar.
Els valors falten a l'atzar quan un buit és igual de probable a qualsevol fila. No falten a l'atzar quan els buits s'amunteguen en un grup concret.
Aquí s'amunteguen en els jocs petits i desconeguts. Escriure 72 en aquestes cel·les diria "un joc decent, segons els crítics" de 23,838 jocs que cap crític no ha obert mai. I escriure 0 seria pitjor: "el pitjor joc mai ressenyat".
Decisió: deixar-ho NULL, i afegir l'indicador (metascore IS NOT NULL) AS has_metascore. Qualsevol gràfic de puntuacions haurà de dir "basat en l'11% dels jocs que van ser ressenyats".
price |
metascore |
|
|---|---|---|
| Buit | 11.6% | 89.3% |
| Sabem què vol dir el buit? | Sí: free to play | No: ningú no l'ha ressenyat |
| Falta a l'atzar? | No ve al cas: coneixem el valor | No: falten els jocs petits |
| Decisió | Constant 0 + is_free |
Deixar NULL + has_metascore |
La mateixa eina SQL, un veredicte oposat.
Columnes de text: una etiqueta per al buit¶
Una columna de text no té mitjana. Per a developer i publisher, dona al buit la seva pròpia categoria, perquè GROUP BY el mostri en lloc d'amagar-lo:
SELECT COALESCE(developer, 'Unknown') AS developer, COUNT(*) AS games
FROM games GROUP BY 1 ORDER BY games DESC LIMIT 3;
| developer | games |
|---|---|
| Unknown | 152 |
| Choice of Games | 92 |
| KOEI TECMO GAMES CO., LTD. | 70 |
Compte: Unknown no és un estudi
Segons aquest resultat, el desenvolupador més gran de la taula és Unknown, amb 152 jocs. És una trampa: són 152 jocs diferents de què no coneixem el desenvolupador, no un estudi que n'hagi fet 152. L'etiqueta manté els buits visibles. No els converteix en una empresa.
NULLIF: quan un valor hauria de ser un buit¶
El problema contrari també existeix: un valor que hauria de ser buit i no ho és. L'auditoria el va trobar a average_playtime: 0 per al 96.5% dels jocs, perquè ningú no ho va mesurar.
NULLIF(x, y)retornaNULLquanxés igual ay, ixen cas contrari. Torna a convertir un valor fals en un buit honest.
SELECT ROUND(AVG(average_playtime)::numeric, 1) AS with_zeros,
ROUND(AVG(NULLIF(average_playtime, 0))::numeric, 1) AS zeros_as_null,
COUNT(NULLIF(average_playtime, 0)) AS measured
FROM games;
| with_zeros | zeros_as_null | measured |
|---|---|---|
| 9.1 | 262.1 | 922 |
Una mitjana de 9.1 amb els zeros falsos, 262.1 sense ells, calculada sobre els 922 jocs que sí que es van mesurar. Gairebé trenta vegades més gran. Els zeros estaven arrossegant la mitjana fins a no-res.
| Funció | Direcció | Usa-la quan |
|---|---|---|
COALESCE(x, value) |
Buit → valor | Saps què vol dir el buit |
NULLIF(x, value) |
Valor → buit | Un valor només és una disfressa de "sense dades" |
El registre d'imputació¶
Un registre d'imputació és l'anotació escrita de cada decisió d'imputació: columna, quant faltava, què has fet i per què.
Aquest és el de games. Cada línia és una fila del quadern:
| Columna | Absents | Decisió | Per què |
|---|---|---|---|
price |
3,095 (11.6%) | Constant 0, més l'indicador is_free |
Coneixement del domini: sense preu a Steam vol dir free to play. Cap fila no té preu 0, i els buits inclouen Dota 2 i Team Fortress 2 |
metascore |
23,838 (89.3%) | Deixar NULL, més l'indicador has_metascore |
Massa, i no falten a l'atzar: el 27.0% dels jocs més grans té puntuació, el 2.7% dels més petits. Qualsevol valor omplert seria inventat |
developer, publisher |
152 i 120 (menys de l'1%) | Constant 'Unknown' |
Una categoria, no un número. L'etiqueta manté les files visibles a cada GROUP BY |
game |
3 (0.01%) | Esborrar les files | Un joc sense nom no es pot identificar ni imputar. 3 files de 26,688 no canvien res |
Compte: esborrar és l'últim recurs, no el primer
Esborrar totes les files amb un buit és la manera més ràpida de "netejar" i normalment l'equivocada. Si elimines tots els jocs sense metascore te'n queden 2,850 de 26,688 files: les teves conclusions descriurien un món més petit i més famós. Esborrar és just per a 3 files sense nom. No ho és per al 89% d'una taula.
Prova-ho: escriu el teu registre d'imputació¶
Obre SQL → SQL Lab, base de dades Dades classe, esquema public. Totes les consultes són un SELECT; no es modifica res. Executa una sentència cada vegada.
Pas 1 — Mesura el forat. Executa això per a price, i repeteix-ho per a metascore, developer i publisher:
SELECT COUNT(*) AS total_rows,
COUNT(price) AS with_value,
COUNT(*) - COUNT(price) AS missing,
ROUND(100.0 * (COUNT(*) - COUNT(price)) / COUNT(*), 1) AS pct_missing
FROM games;
Hauries de veure 11.6, 89.3, 0.6 i 0.4.
Pas 2 — price. Executa les tres consultes de proves, i després la consulta de les dues mitjanes. Escriu quina mitjana publicaries, i per a quina pregunta.
Pas 3 — metascore. Executa els dos recomptes amb 72 i la consulta "qui falta". Escriu una frase: per què 72 és una mentida per a un joc que ningú no ha ressenyat?
Pas 4 — Columnes de text. Executa la consulta COALESCE(developer, 'Unknown'). Qui és el "desenvolupador més gran", i per què és una trampa?
Pas 5 — El registre. Escriu una línia per columna: esborrar, constant, valor central, o deixar-ho buit i marcar-ho.
pct_missing mostra 0 o 11 en lloc d'11.6
Divisió entera. 100 * 3095 / 26688 amb números sencers dona un número sencer. Escriu 100.0 *, amb el decimal, abans de dividir, i els decimals tornen.
function round(double precision, integer) does not exist
ROUND(x, 2) amb un nombre de decimals només funciona amb el tipus numeric. Afegeix ::numeric abans d'arrodonir: ROUND(AVG(price)::numeric, 2).
Idees clau¶
- La imputació és decidir què ocupa el lloc d'un valor absent. És una suposició escrita en SQL, així que s'ha de justificar i deixar per escrit.
AVG,SUMiCOUNT(column)saltenNULL; l'aritmètica i les comparacions ambNULLdonenNULL. Una mitjana amaga sobre quantes files s'ha calculat.COALESCE(x, value)omple un buit; fes-lo sempre acompanyar d'una columna indicadora que recordi que el valor era buit.NULLIF(x, value)fa el contrari.- Usa una constant quan saps què vol dir el buit (
price: gratuït vol dir 0), amb coneixement del domini i proves. - Usa un valor central (mitjana o mediana) només per a pocs buits que falten a l'atzar.
- Deixa'l
NULLi marca'l quan no ho saps o la major part de la columna és buida: omplirmetascoreamb 72 converteix 114 valors reals en 23,952. - Esborra files només quan no serveixen de res i són molt poques.
Comprovació ràpida¶
1. AVG(price) sobre games retorna 8.95 i AVG(COALESCE(price, 0)) retorna 7.91. Per què són diferents?
- Perquè
COALESCEarrodoneix els preus cap avall - Perquè
AVGcompta cadaNULLcom a 0 - Perquè
AVGsalta les filesNULL, així que la primera mitjana divideix entre menys jocs - Perquè una de les dues consultes és incorrecta
AVG salta les cel·les buides: 8.95 es calcula sobre els 23,593 jocs amb preu. Amb COALESCE, els 3,095 buits es converteixen en 0 i entren al càlcul, així que la suma es divideix entre totes les 26,688 files. Totes dues són respostes correctes a preguntes diferents.
2. Què retorna COALESCE(price, 0) per a un joc amb preu 9.99?
- 0
- 9.99
-
NULL - 9.99 i 0, en dues files
COALESCE retorna el primer argument que no és NULL. El preu existeix, així que es retorna intacte. Només els preus buits es converteixen en 0.
3. Per què afegim la columna is_free al costat del preu omplert?
- Perquè
COALESCEno funciona sense ella - Perquè fa la consulta més ràpida
- Perquè un cop el
NULLs'ha substituït per 0, res més no recorda que el valor era buit - Perquè els preus no poden ser 0 a PostgreSQL
Una columna indicadora és la nota al marge del quadern. Després de l'omplert, un 0 inventat i un 0 real es veuen igual. L'indicador conserva la diferència, així que qualsevol pot desfer o qüestionar la decisió més endavant.
4. Per què deixem metascore buit en lloc d'omplir-lo amb la mitjana, 72?
- Perquè
COALESCEno pot omplir una columna numèrica - Perquè la mitjana de la columna canviaria molt
- Perquè falta el 89.3% de la columna i no a l'atzar, així que nou de cada deu valors serien inventats
- Perquè 72 és una puntuació massa baixa per a la majoria de jocs
Omplir converteix 114 puntuacions reals de 72 en 23,952, un pic gegant, mentre que la mitjana gairebé no es mou (71.9 a 72.0) i amaga el desastre. I els buits no falten a l'atzar: s'amunteguen en jocs petits que cap crític no ha ressenyat.
5. Què fa NULLIF(average_playtime, 0)?
- Substitueix cada
NULLper 0 - Esborra les files on el temps de joc és 0
- Retorna
NULLon el temps de joc és 0, i el temps de joc a la resta de files - Compta les files on el temps de joc és 0
NULLIF és el contrari de COALESCE: torna a convertir en un buit honest un valor que només volia dir "sense dades". El temps de joc mitjà passa de 9.1 a 262.1, ara calculat sobre els 922 jocs que sí que es van mesurar.
6. Després de COALESCE(developer, 'Unknown'), el desenvolupador amb més jocs és Unknown, amb 152. Quina és la lectura correcta?
- Un estudi anomenat Unknown ha fet 152 jocs
- 152 jocs diferents no tenen cap desenvolupador registrat; no són un sol estudi
- La consulta és incorrecta i s'ha d'esborrar
- 152 jocs tenen el desenvolupador escrit de dues maneres
L'etiqueta manté els buits visibles en un GROUP BY en lloc d'amagar-los. Però és una categoria per a "no ho sabem", no una empresa. Classificar-la com el desenvolupador més gran seria un error d'interpretació, no d'SQL.
7. Una taula de socis d'un gimnàs té una age buida en el 4% de les files i una cancellation_date buida en el 80% de les files. Tria les dues decisions que pots defensar.
-
age: omplir amb l'edat mediana i afegir un indicador, perquè els buits són pocs i res no suggereix que siguin un grup especial -
cancellation_date: omplir els buits amb la data més comuna -
cancellation_date: deixar-hoNULLi afegir un indicador comis_active, perquè buit vol dir "no s'ha donat de baixa" - Esborrar cada soci amb qualsevol cel·la buida
Dos buits, dues històries. Pocs i a l'atzar: un valor central més un indicador és just. La data de cancel·lació buida vol dir alguna cosa (el soci continua actiu), així que posar-hi una data inventaria el 80% de la columna. Esborrar descartaria la major part del gimnàs.
Les cel·les buides ja tenen una decisió i una raó escrita. Has deixat de reparar columnes; és hora d'inventar-ne de noves. A continuació: Variables sintètiques, on finalment es construeix la taula neta games_clean.