Variables sintètiques¶
Exercici synthetic-variables-annex.pdf · PDF
Compres tres coses al supermercat i mires el tiquet. El preu de cada producte era al prestatge. Però la línia que diu "3 × 1.20 = 3.60", i el TOTAL de baix de tot? Ningú no les va enganxar a cap prestatge. Les va calcular la caixa.
Les teves dades són igual. El fitxer dels jocs de Steam et diu quanta gent té cada joc i què costa. No et diu quants diners ha mogut cada joc, ni quina part del catàleg és gratuïta. Aquestes respostes no són a la taula. Les has de fabricar. Al joc de crafting, aquest és el moment en què deixes de classificar botí i comences a fabricar: objectes nous, construïts amb els que ja tens.
Què aprendràs
- Què és una variable sintètica, i per què no són "dades inventades"
- Com desar la teva neteja com una sola taula,
games_clean, amb una columna que la base de dades calcula tota sola - La diferència entre una columna calculada (un valor per fila) i una mètrica (un valor per grup)
- Com afegir-les totes dues a un dataset de Superset
- Com una funció de finestra escriu el valor d'un grup al costat de cada fila, i et dona una proporció del total en una sola consulta
- La trampa: per què
est_revenueno és el que un joc va guanyar realment
Columnes que ningú no ha recollit¶
Una variable sintètica és una columna que ningú no ha recollit. Es calcula a partir de les columnes que sí que es van recollir.
"Sintètica" no vol dir falsa. Vol dir composta. El total d'una línia del tiquet no és una mentida: és 3 × 1.20, i qualsevol ho pot comprovar. Una variable sintètica és honesta pel mateix motiu: ve amb una recepta.
Ja n'has fet algunes sense dir-ne així. A Categorització vas construir price_band a partir de price. A Imputació vas construir is_free i has_metascore. Avui posem nom a la idea i aprenem les tres maneres de fer-ho. Mantindrem el tiquet durant tota la pàgina:
| Al tiquet | Què és | A SQL i Superset |
|---|---|---|
| Total de línia (unitats × preu) | Un valor nou per a cada fila, fet només amb aquella fila | Columna calculada: owners_min * price |
TOTAL de baix de tot |
Un valor per a tot el grup; les línies desapareixen | Mètrica: SUM(owners_min * price) |
| "Aquesta línia és el 40% del teu tiquet" | Un valor de grup escrit al costat de cada fila; les línies es queden | Funció de finestra: SUM(...) OVER () |
flowchart LR
R["<b>Files de la taula</b><br>un joc per fila"]
R --> C["<b>Columna calculada</b><br>el total de línia:<br>26,685 files d'entrada, 26,685 files de sortida"]
R --> M["<b>Mètrica</b><br>el TOTAL:<br>26,685 files d'entrada, 1 fila per grup de sortida"]
R --> W["<b>Funció de finestra</b><br>la proporció del tiquet:<br>26,685 files d'entrada, 26,685 files de sortida,<br>cadascuna amb el valor del seu grup"]
Primer, fes permanent la neteja¶
Cada arranjament que has escrit en aquesta unitat era un SELECT. Mostrava un resultat net i desapareixia quan tancaves la pestanya. La taula games no va canviar mai. Abans de fabricar res de nou, desa la neteja com una taula real.
Ja coneixes l'eina de Datasets i destí: CREATE TABLE ... AS SELECT desa el resultat d'una consulta com una taula nova. El guió de classe games_clean.sql posa tots els passos de neteja de la unitat en una sola sentència. Llegeix-lo i reconeixeràs cada línia:
| Línia del guió | On ho vas aprendre |
|---|---|
TRIM(game), i release_date de text a un DATE de veritat amb TO_DATE |
Tipus i formats |
owners → owners_min: SPLIT_PART + REGEXP_REPLACE + ::bigint |
Unitats i duplicats |
price_band i owners_tier amb CASE WHEN |
Categorització |
COALESCE(price, 0) + is_free, 'Unknown', has_metascore, WHERE game IS NOT NULL |
Imputació |
ALTER TABLE ... ADD COLUMN release_year ... GENERATED ALWAYS AS (...) STORED |
Avui |
El guió complet, games_clean.sql
DROP TABLE IF EXISTS games_clean;
CREATE TABLE games_clean AS
SELECT
number,
TRIM(game) AS game,
COALESCE(NULLIF(TRIM(developer), ''), 'Unknown') AS developer,
COALESCE(NULLIF(TRIM(publisher), ''), 'Unknown') AS publisher,
CASE WHEN release_date ~ '^[A-Z][a-z]{2} [0-9]{1,2}, [0-9]{4}$' THEN TO_DATE(release_date, 'Mon DD, YYYY')
WHEN release_date ~ '^[A-Z][a-z]{2} [0-9]{4}$' THEN TO_DATE(release_date, 'Mon YYYY')
END AS release_date,
COALESCE(price, 0)::numeric(8,2) AS price,
(price IS NULL) AS is_free,
CASE WHEN price IS NULL THEN 'Free' WHEN price < 5 THEN 'Under 5' WHEN price < 15 THEN '5 to 15'
WHEN price < 30 THEN '15 to 30' ELSE '30 or more' END AS price_band,
REGEXP_REPLACE(SPLIT_PART(owners, '..', 1), '[^0-9]', '', 'g')::bigint AS owners_min,
CASE WHEN REGEXP_REPLACE(SPLIT_PART(owners, '..', 1), '[^0-9]', '', 'g')::bigint < 20000 THEN 'Niche'
WHEN REGEXP_REPLACE(SPLIT_PART(owners, '..', 1), '[^0-9]', '', 'g')::bigint < 1000000 THEN 'Mid'
ELSE 'Hit' END AS owners_tier,
average_playtime, median_playtime,
metascore,
(metascore IS NOT NULL) AS has_metascore
FROM games WHERE game IS NOT NULL;
ALTER TABLE games_clean ADD COLUMN release_year int
GENERATED ALWAYS AS (EXTRACT(YEAR FROM release_date)::int) STORED;
Comença amb DROP TABLE IF EXISTS, així que el pots executar tantes vegades com vulguis: sempre reconstrueix la taula des de zero.
L'última línia és la primera variable sintètica del dia.
Una columna generada és una columna que la base de dades calcula tota sola a partir d'altres columnes de la mateixa fila, i manté actualitzada.
Ningú no escriu un release_year. PostgreSQL llegeix release_date i n'escriu l'any al costat:
SELECT game, release_date, release_year
FROM games_clean ORDER BY number LIMIT 3;
| game | release_date | release_year |
|---|---|---|
| Half-Life 2 | 2004-11-16 | 2004 |
| Sid Meier's Civilization IV | 2005-10-25 | 2005 |
| Company of Heroes | 2006-09-11 | 2006 |
Comprova que la teva taula és completa:
SELECT COUNT(*) FROM games_clean;
La resposta és 26,685: les 26,688 files de games menys els tres jocs sense nom.
Després registra la taula perquè Superset la pugui representar: Datasets → + Dataset → Database Dades classe, Schema public, Table games_clean → Create dataset (la fletxa al costat de Create and explore dataset).
Guarda aquesta taula
games_clean és la taula de la qual es dibuixen tots els gràfics de la Unitat 3 i tots els quadres de comandament (dashboards) de la Unitat 4. Construeix-la una vegada, comprova els 26,685, i deixa-la en pau.
El total de línia: una columna calculada¶
Una columna calculada és una columna nova el valor de la qual es calcula fila per fila, fent servir només els valors d'aquella mateixa fila.
Al tiquet és "3 × 1.20 = 3.60". La caixa no necessita res més que aquella línia per imprimir-ho.
El nostre total de línia: quants diners va moure un joc, més o menys? Pren els propietaris i multiplica pel preu. L'anomenem est_revenue, de estimated revenue (ingressos estimats).
SELECT game, owners_min, price, owners_min * price AS est_revenue
FROM games_clean ORDER BY est_revenue DESC LIMIT 5;
| game | owners_min | price | est_revenue |
|---|---|---|---|
| PLAYERUNKNOWN'S BATTLEGROUNDS | 50,000,000 | 29.99 | 1,499,500,000.00 |
| MONSTER HUNTER: WORLD | 5,000,000 | 59.99 | 299,950,000.00 |
| Sid Meier's Civilization V | 10,000,000 | 29.99 | 299,900,000.00 |
| Grand Theft Auto V | 10,000,000 | 29.99 | 299,900,000.00 |
| ARK: Survival Evolved | 5,000,000 | 49.99 | 249,950,000.00 |
Llegeix la primera xifra en veu alta: mil quatre-cents noranta-nou milions. Uns 1.500 milions de dòlars. Un número tan gros mereix una segona mirada, i li la donarem al final d'aquesta pàgina.
A SQL Lab aquesta columna només viu dins de la teva consulta. Per tenir-la a punt per a tots els gràfics futurs, afegeix-la al dataset:
| Pas | Fes això | Hauries de veure |
|---|---|---|
| 1 | Datasets → a la fila games_clean, clica el llapis sota Actions (Edit) |
L'editor del dataset |
| 2 | Pestanya Calculated columns → Add item | Una fila nova buida |
| 3 | Posa de nom a la columna est_revenue |
|
| 4 | A SQL expression, esborra el text d'exemple i escriu owners_min * price |
|
| 5 | Data type: NUMERIC |
|
| 6 | Save → OK | est_revenue apareix entre les columnes del dataset |
Les altres columnes calculades segueixen el mateix patró. El preu amb un 21% d'IVA és price * 1.21: una fila d'entrada, un valor de sortida.
El TOTAL: una mètrica¶
Una mètrica és un càlcul sobre moltes files que retorna un valor per grup. Sempre és un agregat:
SUM,AVG,COUNT,MIN,MAX.
Al tiquet és el TOTAL. Les línies desapareixen i queda un sol número. Vas conèixer els agregats a SQL Lab: grups; una mètrica és un agregat amb nom, desat al dataset perquè no l'hagis de tornar a escriure mai.
N'afegeixes dues, al mateix editor: pestanya Metrics → Add item, dues vegades.
| Clau de la mètrica | Expressió SQL | Què respon |
|---|---|---|
total_est_revenue |
SUM(owners_min * price) |
Quants ingressos estimats hi ha en total? |
pct_free |
100.0 * SUM(CASE WHEN is_free THEN 1 ELSE 0 END) / COUNT(*) |
Quin percentatge dels jocs és gratuït? |
Després Save → OK.
Mira pct_free. CASE WHEN is_free THEN 1 ELSE 0 END converteix cada joc en un 1 o un 0. Sumar-los compta els gratuïts. Dividir entre COUNT(*) en fa una proporció. El 100.0, amb el seu punt decimal, evita que PostgreSQL faci una divisió entera i respongui 0.
Una mètrica no té una mida fixa. Respon per al grup que li donis. Per a tota la taula:
SELECT SUM(owners_min * price) AS total_est_revenue,
ROUND(100.0 * SUM(CASE WHEN is_free THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_free
FROM games_clean;
| total_est_revenue | pct_free |
|---|---|
| 26,685,821,600.00 | 11.6 |
I amb un GROUP BY owners_tier, les mateixes dues mètriques responen tres vegades:
SELECT owners_tier,
SUM(owners_min * price) AS total_est_revenue,
ROUND(100.0 * SUM(CASE WHEN is_free THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct_free
FROM games_clean GROUP BY owners_tier ORDER BY owners_tier;
| owners_tier | total_est_revenue | pct_free |
|---|---|---|
| Hit | 18,256,160,000.00 | 29.2 |
| Mid | 8,429,661,600.00 | 18.5 |
| Niche | 0.00 | 7.8 |
Gairebé tres de cada deu Hits són gratuïts. I el nivell Niche ha guanyat... zero? Guarda't aquest pensament.
Compte: la mètrica no pot veure la teva columna calculada
Temptador: escriure la mètrica com SUM(est_revenue). Superset la desa sense queixar-se. Després el gràfic falla: column "est_revenue" does not exist.
La columna calculada viu al dataset de Superset, no a la taula de PostgreSQL. La caixa imprimeix el total de línia; l'etiqueta del prestatge mai no el va tenir. Escriu la mètrica amb l'expressió completa: SUM(owners_min * price).
L'error contrari és igual de dolent: mai no posis un agregat dins d'una columna calculada. Una línia del tiquet no pot contenir el TOTAL.
La proporció del tiquet: funcions de finestra¶
De vegades vols les dues coses alhora: cada fila, i el total al costat. "Aquesta línia és el 40% del teu tiquet." Amb GROUP BY no pots: les files ja no hi són.
Una funció de finestra calcula un valor sobre un grup de files i l'escriu al costat de cada fila, sense col·lapsar-les. La reconeixes per la paraula
OVER.
Llegeix OVER () com "sobre totes les files, sense col·lapsar-les". Aquí dona la proporció de cada franja de preu:
SELECT price_band, COUNT(*) AS games,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS pct_of_games
FROM games_clean GROUP BY price_band ORDER BY games DESC;
| price_band | games | pct_of_games |
|---|---|---|
| Under 5 | 11,613 | 43.5 |
| 5 to 15 | 9,046 | 33.9 |
| Free | 3,094 | 11.6 |
| 15 to 30 | 2,367 | 8.9 |
| 30 or more | 565 | 2.1 |
COUNT(*) és la línia. SUM(COUNT(*)) OVER () és el TOTAL, imprès a cada línia: 26,685 cinc vegades. Divideix l'un per l'altre i tens una proporció del total en una sola consulta, sense cap subconsulta.
Una finestra també es pot tallar en grups.
PARTITION BYés elGROUP BYd'una finestra: divideix les files en grups, però les files es queden.
Aquesta consulta compara cada joc amb el preu mitjà del seu propi any de llançament:
SELECT game, release_year, price,
ROUND(AVG(price) OVER (PARTITION BY release_year), 2) AS year_avg,
ROUND(price - AVG(price) OVER (PARTITION BY release_year), 2) AS vs_year_avg
FROM games_clean ORDER BY owners_min DESC, number LIMIT 5;
| game | release_year | price | year_avg | vs_year_avg |
|---|---|---|---|---|
| Dota 2 | 2013 | 0.00 | 11.21 | -11.21 |
| Team Fortress 2 | 2007 | 0.00 | 8.59 | -8.59 |
| PLAYERUNKNOWN'S BATTLEGROUNDS | 2017 | 29.99 | 7.69 | 22.30 |
| Counter-Strike: Global Offensive | 2012 | 0.00 | 10.96 | -10.96 |
| Paladins | 2018 | 0.00 | 7.12 | -7.12 |
Cinc files, cinc jocs, i cadascun porta la mitjana del seu any. PUBG va costar 22.30 dòlars més que el joc mitjà del 2017. vs_year_avg és una variable sintètica construïda a partir d'un càlcul d'agregació.
GROUP BY |
Funció de finestra | |
|---|---|---|
| Files que obtens | Una per grup | Totes les files |
| On va el valor del grup | Substitueix les files | Seu al costat de cada fila |
| Paraula clau | GROUP BY publisher |
OVER (PARTITION BY publisher) |
| Al tiquet | El TOTAL |
"Aquesta línia és el 40% del teu tiquet" |
Compte: WHERE s'executa abans de la finestra
Si filtres amb WHERE, el "total" és el total del que queda. Afegeix WHERE price_band <> 'Free' a una consulta de proporcions i els percentatges continuen sumant 100, però és el 100% dels jocs de pagament, no del catàleg. Digues sempre en veu alta què és el teu 100%.
Què no és est_revenue¶
Ara, la segona mirada que havíem promès. Aquí tens la proporció de jocs i la proporció d'ingressos estimats per nivell:
SELECT owners_tier, COUNT(*) AS games,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 1) AS pct_games,
ROUND(100.0 * SUM(owners_min * price) / SUM(SUM(owners_min * price)) OVER (), 1) AS pct_revenue
FROM games_clean GROUP BY owners_tier ORDER BY pct_revenue DESC;
| owners_tier | games | pct_games | pct_revenue |
|---|---|---|---|
| Hit | 623 | 2.3 | 68.4 |
| Mid | 8,141 | 30.5 | 31.6 |
| Niche | 17,921 | 67.2 | 0.0 |
623 jocs de 26,685 s'enduen dos terços dels diners. Aquest és un patró real i interessant.
Però 17,921 jocs no han guanyat res? No. Recorda d'on venia owners_min: de la part baixa d'un rang. El fitxer original diu "0 .. 20,000", i la part baixa d'aquest rang és 0. Zero propietaris per qualsevol preu és zero.
La variable només és tan bona com les columnes amb què s'ha fet. Així que est_revenue ha de viatjar amb la seva etiqueta:
| Què és | Què no és |
|---|---|
| El mínim de propietaris × el preu el dia que es van recollir les dades | El que el joc va guanyar realment |
| Un mínim, útil per comparar jocs entre ells | Una quantitat que puguis citar en euros o dòlars |
| 0 per a cada joc gratuït, Dota 2 inclòs | Una prova que els jocs gratuïts no guanyen diners |
| Cega a les vendes, els descomptes i els canvis de preu | Un registre de vendes |
Una variable sintètica sense la seva recepta és un rumor
Cada vegada que creïs una columna, deixa escrit com. est_revenue = owners_min * price, on owners_min és l'extrem baix d'una estimació. Amb aquesta línia, un lector pot jutjar el teu número. Sense ella, només pot creure-se'l o no.
Prova-ho¶
Fes servir games_clean a SQL Lab (base de dades Dades classe, esquema public). El fitxer original, video_games.csv, és a la pàgina de Recursos.
Pas 1 — Afegeix la columna calculada est_revenue i les mètriques total_est_revenue i pct_free al teu dataset, seguint les taules de dalt.
Pas 2 — Primer prediu, després executa: quin nivell té el pct_free més alt? Comprova-ho amb la consulta GROUP BY owners_tier.
Pas 3 — Acaba la frase amb l'última consulta: "El nivell Hit és el __% dels jocs i el __% dels ingressos estimats."
Pas 4 — Fes un rànquing dins d'un grup. RANK() és una funció de finestra que numera les files de cada grup. Quins són els jocs amb més propietaris del 2017?
SELECT game, owners_min,
RANK() OVER (PARTITION BY release_year ORDER BY owners_min DESC) AS position
FROM games_clean WHERE release_year = 2017 ORDER BY position LIMIT 4;
Hauries d'obtenir PLAYERUNKNOWN'S BATTLEGROUNDS (1), Unturned (2), i Robocraft i Brawlhalla compartint la posició 3: empaten a 10,000,000.
Pas 5 — Escriu una raó per la qual est_revenue no és el que un joc va guanyar realment. Si en saps escriure una, has entès la lliçó.
Idees clau¶
- Una variable sintètica és una columna que ningú no ha recollit: es calcula a partir de les que sí. És honesta mentre porti la seva recepta.
CREATE TABLE ... AS SELECTfa permanent la neteja agames_clean(26,685 files). Una columna generada comrelease_yearla calcula la mateixa base de dades.- Una columna calculada dona un valor per fila (
owners_min * price). Una mètrica dona un valor per grup (SUM(owners_min * price)). - Una mètrica ha d'usar l'expressió completa: les columnes calculades de Superset no existeixen a la taula de PostgreSQL.
- Una funció de finestra (
OVER,PARTITION BY) escriu el valor d'un grup al costat de cada fila.COUNT(*) / SUM(COUNT(*)) OVER ()és una proporció del total. est_revenueés un mínim, no uns ingressos: una variable només és tan bona com les columnes amb què s'ha fet.
Comprovació ràpida¶
1. Què és una variable sintètica?
- Una columna omplerta amb valors inventats per amagar dades absents
- Una columna que ningú no ha recollit, calculada a partir de les columnes que sí
- Una columna copiada d'una altra taula amb un
JOIN - Una columna els valors de la qual s'han escrit a mà després de la càrrega
Sintètica vol dir composta, no falsa. release_year, price_band i est_revenue mai no van ser al fitxer: cadascuna es calcula a partir de columnes existents amb una recepta que qualsevol pot comprovar.
2. Al tiquet del supermercat, quina part és la mètrica?
- El preu imprès a l'etiqueta del prestatge
- El total de línia, unitats × preu
- El TOTAL de baix de tot
- La nota "aquesta línia és el 40% del teu tiquet"
Una mètrica és un agregat: entren moltes files i surt un valor per grup, com el TOTAL. El total de línia és una columna calculada i la nota del 40% és una funció de finestra.
3. Has creat la columna calculada est_revenue a l'editor del dataset i després una mètrica SUM(est_revenue). El gràfic falla i diu que la columna est_revenue no existeix. Per què?
- Perquè
SUMno funciona amb columnesNUMERIC - Perquè un dataset pot tenir una columna calculada o una mètrica, però no totes dues
- Perquè la columna calculada viu al dataset de Superset, no a la taula de PostgreSQL
- Perquè els noms de les mètriques no poden contenir un guió baix
La consulta s'envia a PostgreSQL, i la taula games_clean no té cap columna anomenada est_revenue: aquesta columna només existeix al dataset de Superset. Escriu la mètrica amb l'expressió completa, SUM(owners_min * price).
4. Quina diferència hi ha entre GROUP BY publisher i OVER (PARTITION BY publisher)?
- Cap: són dues grafies de la mateixa cosa
-
GROUP BYfunciona amb números iPARTITION BYfunciona amb text -
PARTITION BYesborra les files que no pertanyen a un grup -
GROUP BYretorna una fila per editor; la finestra retorna cada fila amb el valor del seu editor al costat
Les dues divideixen les files en grups. GROUP BY col·lapsa cada grup en una fila. Una funció de finestra conserva cada fila i hi escriu al costat el valor del grup, per això pots restar el preu d'un joc de la mitjana del seu any.
5. A la consulta de proporcions, què et dona SUM(COUNT(*)) OVER ()?
- El nombre de jocs de cada franja de preu
- El nombre total de jocs, repetit a cada fila del resultat
- El nombre de franges de preu
- Un total acumulat que creix fila a fila
COUNT(*) és la línia: els jocs d'una franja. SUM(COUNT(*)) OVER () suma aquests recomptes sobre totes les files i imprimeix el resultat, 26,685, a cadascuna. Dividir el primer pel segon dona la proporció del total.
6. est_revenue diu que els 17,921 jocs Niche han guanyat 0 en total. Quina és la conclusió correcta?
- Els jocs Niche són tots gratuïts
- La consulta té un error al
SUM -
owners_minés la part baixa del rang0 .. 20,000, així que la variable és un mínim, no uns ingressos reals - Steam no paga els desenvolupadors petits
L'aritmètica és correcta: 0 propietaris × qualsevol preu = 0. El problema és l'ingredient. owners_min és l'extrem baix d'una estimació, i per al rang més petit aquest extrem és 0. Una variable sintètica només és tan bona com les columnes amb què s'ha fet.
7. Per a un dataset nou necessites: el preu de cada joc amb un 21% d'IVA, i el temps de joc mitjà per editor. Tria les dues opcions correctes.
- El preu amb IVA és una columna calculada:
price * 1.21 - El temps de joc mitjà per editor és una mètrica:
AVG(average_playtime), agrupada per editor - El preu amb IVA és una mètrica, perquè fa servir una multiplicació
- El temps de joc mitjà per editor és una columna calculada, perquè cada joc té un editor
Pregunta't "quants valors obtinc?". El preu amb IVA és un valor per fila, només d'aquella fila: una columna calculada. La mitjana és un valor per grup: una mètrica. Si volguessis la posició de cada joc entre els jocs del seu editor, necessitaries una funció de finestra: un càlcul de grup que conserva cada fila.
games_clean ja existeix, amb columnes que mai no van ser al fitxer. A continuació reconstruiràs la mateixa neteja sense escriure ni una línia d'SQL: Power Query, on cliques i l'eina escriu la recepta per tu.