Una consulta és un conjunt d'expressions que s'apliquen a un context
- Introducció
- Expressió
- Context
- select
- with_columns
- filter
- group_by
- Expansió d’expressions
- API Lazy
- Tycho
- Electric Vehicle Population
- Mapa de cultius
Introducció
Crea el projecte polars-query
.
Expressió
A Polars, una expressió és una representació “lazy” d’una transformació de dades.
Les expressions són modulars i flexibles, és a dir, les pots fer servir com a blocs de construcció per crear expressions més complexes.
Aquí tens un exemple d’expressió de Polars:
import polars as pl
pl.col("weight") / (pl.col("height") ** 2)
Com pots intuir, aquesta expressió agafa una columna anomenada “weight” i divideix els seus valors pel quadrat dels valors de la columna “height”, calculant el BMI (o IMC, índex de massa corporal) d’una persona.
El codi anterior expressa un càlcul abstracte que pots desar en una variable, manipular més endavant o simplement imprimir:
bmi_expr = pl.col("weight") / (pl.col("height") ** 2)
print(bmi_expr)
[(col("weight")) / (col("height").pow([dyn int: 2]))]
Com que les expressions són “lazy”, encara no s’ha realitzat cap càlcul.
Context
Les expressions de Polars necessiten un context en el qual s’executen per produir un resultat.
Depenent del context en què s’utilitzi, la mateixa expressió de Polars pot produir resultats diferents.
Com que les expressions són “lazy”, quan utilitzes una expressió dins d’un context, Polars pot intentar simplificar-la abans d’executar la transformació de dades que expressa.
Les expressions separades dins d’un context es poden executar en paral·le i Polars se n’aprofita, alhora que paral·lelitza l’execució d’expressions quan s’utilitza l’expansió d’expressions.
Encara es poden obtenir més guanys de rendiment quan s’utilitza l’API “lazy” de Polars.
En aquesta secció aprendrem sobre els quatre contexts més comuns que ofereix Polars:
select
with_columns
filter
group_by
Farem servir el dataframe següent per mostrar com funciona cada context.
import polars as pl
from datetime import date
df = pl.DataFrame(
{
"name": ["Alice Archer", "Ben Brown", "Chloe Cooper", "Daniel Donovan"],
"birthdate": [
date(1997, 1, 10),
date(1985, 2, 15),
date(1983, 3, 22),
date(1981, 4, 30),
],
"weight": [57.9, 72.5, 53.6, 83.1], # (kg)
"height": [1.56, 1.77, 1.65, 1.75], # (m)
}
)
select
El context de selecció select
aplica expressions sobre columnes.
El context select
pot produir columnes noves que siguin agregacions, combinacions d’altres columnes o literals:
result = df.select(
pl.col("name"),
bmi=bmi_expr,
avg_bmi=bmi_expr.mean(),
ideal_max_bmi=25,
)
print(result)
shape: (4, 4)
┌────────────────┬───────────┬───────────┬───────────────┐
│ name ┆ bmi ┆ avg_bmi ┆ ideal_max_bmi │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ i32 │
╞════════════════╪═══════════╪═══════════╪═══════════════╡
│ Alice Archer ┆ 23.791913 ┆ 23.438973 ┆ 25 │
│ Ben Brown ┆ 23.141498 ┆ 23.438973 ┆ 25 │
│ Chloe Cooper ┆ 19.687787 ┆ 23.438973 ┆ 25 │
│ Daniel Donovan ┆ 27.134694 ┆ 23.438973 ┆ 25 │
└────────────────┴───────────┴───────────┴───────────────┘
Les expressions en un context select
han de produir sèries totes de la mateixa longitud o bé produir un escalar.
Els escalars es difonen (broadcast) per igualar la longitud de les sèries restants. Els literals, com el nombre utilitzat abans, també es difonen.
Tingues en compte que la difusió també pot passar dins de les expressions.
Per exemple, considera l’expressió següent:
result = df.select(
pl.col("name"),
deviation=(bmi_expr - bmi_expr.mean()) / bmi_expr.std()
)
print(result)
shape: (4, 2)
┌────────────────┬───────────┐
│ name ┆ deviation │
│ --- ┆ --- │
│ str ┆ f64 │
╞════════════════╪═══════════╡
│ Alice Archer ┆ 0.115645 │
│ Ben Brown ┆ -0.097471 │
│ Chloe Cooper ┆ -1.22912 │
│ Daniel Donovan ┆ 1.210946 │
└────────────────┴───────────┘
Tant la resta com la divisió utilitzen difusió (broadcasting) dins de l’expressió perquè les subexpressions que calculen la mitjana i la desviació estàndard s’avaluen a valors únics.
El context select
és molt flexible i potent, i et permet avaluar expressions arbitràries de manera independent i en paral·lel entre elles.
Això també és cert per als altres contexts que veurem a continuació.
with_columns
El context with_columns
és molt similar al context select
.
La diferència principal entre tots dos és que el context with_columns
crea un dataframe nou que conté les columnes del dataframe original i les noves columnes segons les seves expressions d’entrada, mentre que el context select
només inclou les columnes seleccionades per les seves expressions d’entrada:
result = df.with_columns(
bmi=bmi_expr,
avg_bmi=bmi_expr.mean(),
ideal_max_bmi=25,
)
print(result)
shape: (4, 7)
┌────────────────┬────────────┬────────┬────────┬───────────┬───────────┬───────────────┐
│ name ┆ birthdate ┆ weight ┆ height ┆ bmi ┆ avg_bmi ┆ ideal_max_bmi │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i32 │
╞════════════════╪════════════╪════════╪════════╪═══════════╪═══════════╪═══════════════╡
│ Alice Archer ┆ 1997-01-10 ┆ 57.9 ┆ 1.56 ┆ 23.791913 ┆ 23.438973 ┆ 25 │
│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 ┆ 23.141498 ┆ 23.438973 ┆ 25 │
│ Chloe Cooper ┆ 1983-03-22 ┆ 53.6 ┆ 1.65 ┆ 19.687787 ┆ 23.438973 ┆ 25 │
│ Daniel Donovan ┆ 1981-04-30 ┆ 83.1 ┆ 1.75 ┆ 27.134694 ┆ 23.438973 ┆ 25 │
└────────────────┴────────────┴────────┴────────┴───────────┴───────────┴───────────────┘
A causa d’aquesta diferència entre select
i with_columns
, les expressions utilitzades en un context with_columns
han de produir sèries que tinguin la mateixa longitud que les columnes originals del dataframe, mentre que en el context select
n’hi ha prou que les expressions produeixin sèries de la mateixa longitud entre elles.
filter
El context filter
filtra les files d’un dataframe basant-se en una o més expressions que avaluen al tipus de dada booleà.
result = df.filter(
pl.col("birthdate").is_between(date(1982, 12, 31), date(1996, 1, 1)),
pl.col("height") > 1.7
)
print(result)
shape: (1, 4)
┌───────────┬────────────┬────────┬────────┐
│ name ┆ birthdate ┆ weight ┆ height │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ date ┆ f64 ┆ f64 │
╞═══════════╪════════════╪════════╪════════╡
│ Ben Brown ┆ 1985-02-15 ┆ 72.5 ┆ 1.77 │
└───────────┴────────────┴────────┴────────┘
group_by
En el context group_by
, les files s’agrupen segons els valors únics de les expressions d’agrupació.
A continuació pots aplicar expressions als grups resultants, que poden tenir longituds variables.
Quan uses el context group_by
, pots fer servir una expressió per calcular les agrupacions dinàmicament:
result = df.group_by(
(pl.col("birthdate").dt.year() // 10 * 10).alias("decade"),
).agg(pl.col("name"))
print(result)
shape: (2, 2)
┌────────┬─────────────────────────────────┐
│ decade ┆ name │
│ --- ┆ --- │
│ i32 ┆ list[str] │
╞════════╪═════════════════════════════════╡
│ 1990 ┆ ["Alice Archer"] │
│ 1980 ┆ ["Ben Brown", "Chloe Cooper", … │
└────────┴─────────────────────────────────┘
Després d’utilitzar group_by
fem servir agg
per aplicar expressions d’agregació als grups.
Com que a l’exemple anterior només vam especificar el nom d’una columna, obtenim els grups d’aquella columna com a llistes.
Pots especificar tantes expressions d’agrupació com vulguis, i el context group_by
agruparà les files segons els valors distintius a través de les expressions especificades.
Aquí agrupem per una combinació de la dècada de naixement i si la persona és més baixa d’1,7 metres:
result = df.group_by(
(pl.col("birthdate").dt.year() // 10 * 10).alias("decade"),
(pl.col("height") < 1.7).alias("short?"),
).agg(pl.col("name"))
print(result)
shape: (3, 3)
┌────────┬────────┬─────────────────────────────────┐
│ decade ┆ short? ┆ name │
│ --- ┆ --- ┆ --- │
│ i32 ┆ bool ┆ list[str] │
╞════════╪════════╪═════════════════════════════════╡
│ 1980 ┆ true ┆ ["Chloe Cooper"] │
│ 1980 ┆ false ┆ ["Ben Brown", "Daniel Donovan"… │
│ 1990 ┆ true ┆ ["Alice Archer"] │
└────────┴────────┴─────────────────────────────────┘
El dataframe resultant, després d’aplicar expressions d’agregació, conté una columna per a cada expressió d’agrupació a l’esquerra i tantes columnes com calgui per representar els resultats de les expressions d’agregació.
Al seu torn, pots especificar tantes expressions d’agregació com vulguis:
result = df.group_by(
(pl.col("birthdate").dt.year() // 10 * 10).alias("decade"),
(pl.col("height") < 1.7).alias("short?"),
).agg(
pl.len(),
pl.col("height").max().alias("tallest"),
pl.col("weight", "height").mean().name.prefix("avg_"),
)
print(result)
shape: (3, 6)
┌────────┬────────┬─────┬─────────┬────────────┬────────────┐
│ decade ┆ short? ┆ len ┆ tallest ┆ avg_weight ┆ avg_height │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i32 ┆ bool ┆ u32 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪════════╪═════╪═════════╪════════════╪════════════╡
│ 1980 ┆ true ┆ 1 ┆ 1.65 ┆ 53.6 ┆ 1.65 │
│ 1990 ┆ true ┆ 1 ┆ 1.56 ┆ 57.9 ┆ 1.56 │
│ 1980 ┆ false ┆ 2 ┆ 1.77 ┆ 77.8 ┆ 1.76 │
└────────┴────────┴─────┴─────────┴────────────┴────────────┘
Expansió d’expressions
__PENDENT moure a select
L’últim exemple contenia dues expressions d’agrupació i tres expressions d’agregació, així i tot, el dataframe resultant contenia sis columnes en comptes de cinc.
Si hi parem atenció, l’última expressió d’agregació esmentava dues columnes diferents: “weight” i “height”.
Les expressions de Polars admeten una funcionalitat anomenada expansió d’expressions.
L’expansió d’expressions és com una notació abreujada per quant vols aplicar la mateixa transformació a múltiples columnes.
Com has vist, l’expressió
pl.col("weight", "height").mean().name.prefix("avg_")
calcula el valor mitjà de les columnes “weight” i “height” i les reanomena “avg_weight” i “avg_height”, respectivament.
[
pl.col("weight").mean().alias("avg_weight"),
pl.col("height").mean().alias("avg_height"),
]
En aquest cas, aquesta expressió s’expandeix en dues expressions independents que Polars pot executar en paral·lel.
En altres casos, pot ser que no sapigues per endavant en quantes expressions independents es desplegarà una expressió.
Considera aquest exemple simple però il·lustratiu:
(pl.col(pl.Float64) * 1.1).name.suffix("*1.1")
Aquesta expressió multiplicarà totes les columnes amb tipus de dada Float64
per 1.1
.
El nombre de columnes a les quals s’aplica depèn de l’esquema de cada dataframe.
En el cas del dataframe que has estat utilitzant, s’aplica a dues columnes:
expr = (pl.col(pl.Float64) * 1.1).name.suffix("*1.1")
result = df.select(expr)
print(result)
shape: (4, 2)
┌────────────┬────────────┐
│ weight*1.1 ┆ height*1.1 │
│ --- ┆ --- │
│ f64 ┆ f64 │
╞════════════╪════════════╡
│ 63.69 ┆ 1.716 │
│ 79.75 ┆ 1.947 │
│ 58.96 ┆ 1.815 │
│ 91.41 ┆ 1.925 │
└────────────┴────────────┘
En el cas del dataframe df2
següent, la mateixa expressió s’expandeix a 0 columnes perquè cap columna té el tipus de dada Float64
:
df2 = pl.DataFrame(
{
"ints": [1, 2, 3, 4],
"letters": ["A", "B", "C", "D"],
}
)
result = df2.select(expr)
print(result)
shape: (0, 0)
┌┐
╞╡
└┘
API Lazy
Polars admet dos modes d’operació: lazy i eager.
Fins ara, els exemples han utilitzat l’API eager, en què la consulta s’executa immediatament.
A l’API lazy, la consulta només s’avalua quan es fa la col·lecció dels resultats (collect).
Ajornar l’execució fins a l’últim moment pot oferir avantatges de rendiment significatius i per això l’API lazy és la preferida en la majoria de casos.
En general, s’hauria de preferir l’API “lazy”, tret que t’interessin els resultats intermedis o estiguis fent treball exploratori i encara no sàpigues com serà la teva consulta.
Vegem-ho amb un exemple:
data_uri = "https://gitlab.com/xtec/python/data/-/raw/main/iris.csv"
df = pl.read_csv(data_uri)
df_small = df.filter(pl.col("sepal_length") > 5)
df_agg = df_small.group_by("species").agg(pl.col("sepal_width").mean())
print(df_agg)
shape: (3, 2)
┌────────────┬─────────────┐
│ species ┆ sepal_width │
│ --- ┆ --- │
│ str ┆ f64 │
╞════════════╪═════════════╡
│ Setosa ┆ 3.713636 │
│ Versicolor ┆ 2.804255 │
│ Virginica ┆ 2.983673 │
└────────────┴─────────────┘
En aquest exemple fas servir l’API eager per:
- Llegir el dataset iris.
- Filtrar el conjunt de dades segons la longitud del sèpal.
- Calcular la mitjana de l’amplada del sèpal per espècie.
Cada pas s’executa immediatament retornant resultats intermedis.
Això pot ser força ineficient, ja que podem estar fent feina o carregant dades addicionals que no s’acabaran utilitzant.
Si en comptes d’això utilitzes l’API “lazy” i esperes a executar fins que estiguin definits tots els passos, el planificador de consultes pot aplicar diverses optimitzacions.
En aquest cas:
-
Predicate pushdown: aplicar els filtres tan aviat com sigui possible mentre es llegeix el conjunt de dades, de manera que només es llegeixin files amb longitud de sèpal superior a 5.
-
Projection pushdown: seleccionar només les columnes necessàries mentre es llegeix el conjunt de dades, evitant carregar columnes addicionals (p. ex., llargada i amplada del pètal).
q = (
pl.scan_csv(data_uri)
.filter(pl.col("sepal_length") > 5)
.group_by("species")
.agg(pl.col("sepal_width").mean())
)
df = q.collect()
Això redueix significativament la càrrega de memòria i CPU, cosa que permet encaixar conjunts de dades més grans a memòria i processar-los més ràpid. Un cop definida la consulta, crides collect
per indicar a Polars que la vols executar.
Pots aprendre més sobre l’API “lazy” al seu capítol dedicat: https://docs.pola.rs/user-guide/lazy/.
Previsualitza el pla de consulta
Quan utilitzes l’API “lazy”, pots fer servir la funció explain
per demanar a Polars que creï una descripció del pla de consulta que s’executarà quan col·leccionis els resultats.
Això pot ser útil per veure quines optimitzacions aplica Polars a les teves consultes
Podem demanar a Polars que expliqui la consulta q
que has definit abans:
print(q.explain())
AGGREGATE
[col("sepal_width").mean()] BY [col("species")] FROM
Csv SCAN [docs/assets/data/iris.csv]
PROJECT 3/5 COLUMNS
SELECTION: [(col("sepal_length")) > (5.0)]
De seguida veiem a l’explicació que Polars ha aplicat el predicate pushdown, ja que només llegeix files on la longitud del sèpal és superior a 5, i també ha aplicat el projection pushdown, ja que només llegeix les columnes necessàries per a la consulta.
La funció explain
també es pot utilitzar per veure com s’expandeixen les expressions en el context d’un esquema donat.
Considera un exemple que hem explicat anteriorment:
(pl.col(pl.Float64) * 1.1).name.suffix("*1.1")
Pots fer servir explain
per veure com s’avaluaria aquesta expressió contra un esquema arbitrari:
schema = pl.Schema(
{
"int_1": pl.Int16,
"int_2": pl.Int32,
"float_1": pl.Float64,
"float_2": pl.Float64,
"float_3": pl.Float64,
}
)
print(
pl.LazyFrame(schema=schema)
.select((pl.col(pl.Float64) * 1.1).name.suffix("*1.1"))
.explain()
)
SELECT [[(col("float_1")) * (1.1)].alias("float_1*1.1"), [(col("float_2")) * (1.1)].alias("float_2*1.1"), [(col("float_3")) * (1.1)].alias("float_3*1.1")] FROM
DF ["int_1", "int_2", "float_1", "float_2"]; PROJECT 3/5 COLUMNS; SELECTION: None
Tycho
Project Tycho té un dataset que conté un historial del recompte de casos de diverses enfermetats que han afectat als EEUU des del 1888 al 2014.
En alguns anys hi ha molts registres (1900-1950) i d’altres menys, però en total podem tenir més d’10M d’observacions; cadascuna de les quals té 10 columnes d’interès.
Per accedir-hi ens podem registrar (és gratuït) però no ens cal, ja que tenim les dades de fa 2 anys i no ens cal que siguin actualitzades per aquest exemple.
Hem penjat un subset de 1M de línies en aquest fitxer tycho-mini.zip (120 MB un cop descomprimit):
from io import BytesIO
from zipfile import ZipFile
import os
import polars as pl
import urllib3
import streamlit as st
filepath = "data/tycho-mini.csv"
if not os.path.isfile(filepath):
load = st.text('Loading data...')
resp = urllib3.request(
"GET",
"https://gitlab.com/xtec/python/data/-/raw/main/tycho-mini.zip",
)
zipfile = ZipFile(BytesIO(resp.data))
zipfile.extractall(path="data")
load.text('Loading data... done!')
df = pl.scan_csv(filepath)
Fixa’t amb les columnes que disposem originalment al fitxer CSV de Tycho:
epi_week | Setmana epidemiològica (de l’1 al 52 normalment, algún cop hi ha 53). És una mètrica necessària i molt habitual en la informàtica mèdica. |
country | País. En aquest dataset només hi ha mostres dels Estats Units, per tant podrem ometre-la. US |
state | Sigles de l’estat dels EEUU. |
loc | Nom complet de l’estat dels EEUU. Guardar state i loc (info redundant) només si volem visualitzar mapes. |
loc_type | En el dataset pot ser CITY o STATE . |
disease | Enfermetat. Entre [] ens indica informació addicional, que potser en el nostre estudi és necessària i ometre-la ens pot estalviar memòria del dataFrame. |
event | Cada event pot ser de 2 tipus, i és important distingir-los segons el que volguem estudiar: CASES (número de casos), DEATHS(número de morts causats per la enfermetat). Si volem treballar bé aquest estudi es pot calcular una ràtio de CASES i DEATHS, que el seu resultat serà entre 0 i 1 (1 si tots els casos han estat mortals) |
number | Important! Número de casos (si event=‘CASES’) o número de morts (si event=‘DEATHS’) |
from_date to_date | Dates d’inici i de fi en què es mesura el número de casos. Són (o haurien de ser) intèrvals d’una setmana. |
url | El projecte Tycho ha escanejat i/o digitalitzat documents de paper a PDF (anys 1980 i anteriors) que demostren els registres realitzats i han de ser molt interessants. Desgraciadament l’enllaç proporcionat no funciona. |
Finalment, cal remarcar que podem observar que el dataset està en Format Tidy:
- Cada fila és una observació
- Cada columna és una variable
- Cada valor té una única dada
Si el dataset no fos Tidy, hauries de preprocessar-lo i arreglar-lo fins que ho sigui.
Encara que sigui un historial de malalties de fa molts anys, la forma d’organitzar les dades que s’usa avui en dia és força semblant, bancs de dades amb format Tidy.
Llista de totes les ciutats que surten al fitxer, que no es repeteixin.
Us n’haurien de sortir 247.
┌──────────────────┐
│ city │
╞══════════════════╡
│ OAKLAND │
│ ANN ARBOR │
│ BIDDEFORD │
│ SARATOGA SPRINGS │
│ … │
│ BENNINGTON │
│ BRADDOCK │
│ CHARLOTTE │
│ CHICAGO │
└──────────────────┘
df.select('city').unique()
Llista el número total de morts de cada malaltia, ordenada pel número de morts.
┌────────────────┬────────┐
│ disease ┆ deaths │
╞════════════════╪════════╡
│ TUBERCULOSIS ┆ 181972 │
│ SCARLET FEVER ┆ 110893 │
│ DIPHTHERIA ┆ 100049 │
│ TYPHOID FEVER ┆ 44291 │
│ WHOOPING COUGH ┆ 14713 │
└────────────────┴────────┘
(df.groupby('disease').
agg(pl.col('deaths').sum()).
sort('deaths', descending=True))
Mostra el número de morts per la tuberculosi, a Nova York, l’any 1910: 32403
tuberculosis_ny_1910 = fixed_entries.filter(
(pl.col('disease') == 'TUBERCULOSIS') &
(pl.col('city') == 'NEW YORK') &
(pl.col('year') == 1910)
)
# Sumar el número de morts
num_deaths = tuberculosis_ny_1910.select(pl.col('deaths').sum()).item()
st.write(f"Nombre de morts per TUBERCULOSIS a NEW YORK l'any 1910: {num_deaths}")
- Elimina les columnes
country
iurl
- Reanomena
evnt
aevent
- Neteja les malalties eliminant els noms entre claudàtors.
- Afegeix una nova columna anomenada
year
de tipusint
amb l’any extret de l’epi_week
. - Selecciona les files on l’any és 1910 o 1911.
- Afegeix una nova columna anomenada
id
amb un identificador únic numèric que comenci des de 0 - Reanomena
loc
acity
,number
adeaths
- Reordena les columnes de la manera següent: [‘id’, ‘year’, ‘epi_week’, ‘from_date’, ‘to_date’, ‘state’, ‘city’, ‘disease’, ‘deaths’]
def get_year(epi_week: int) -> int:
"""
Transforms an epidemic week and returns the year.
"""
epi_week_str: str = str(epi_week)
year_str: str = epi_week_str[0:4]
year_int: int = int(year_str)
return year_int
df = (
df.drop(["country", "url"]) # Elimina columnes innecessàries
.rename({"loc": "city", "number": "deaths"}) # Reanomena columnes
.with_columns(
pl.col("disease")
.str.replace_all(r" \[.*\]", "")
.alias("disease"), # Neteja la columna 'disease'
(pl.col("epi_week") // 100).cast(pl.Int32).alias("year"), # Extreu l'any
)
.filter(pl.col("year").is_in([1910, 1911]))
.with_row_count(name="id") # Afegeix una columna 'id' que comença des de 0
.select( # Reordena les columnes
["id", "year", "epi_week", "from_date", "to_date", "state", "city", "disease", "deaths"]
)
)
Electric Vehicle Population
Treballaràs amb dades de població de vehicles elèctrics.
Aquest conjunt de dades conté informació sobre vehicles elèctrics i híbrids registrats al Departament de Llicències de l’Estat de Washington.
Cada fila de les dades representa un cotxe, i cada columna conté informació sobre el cotxe.
El conjunt de dades està en format CSV, i pots accedir-hi mitjançant l’enllaç següent: https://data.wa.gov/api/views/f6w7-q2d2/rows.csv?accessType=DOWNLOAD
filepath = "data/electric-cars.csv"
if not os.path.isfile(filepath):
load = st.write("Downloading data")
response = urllib3.request(
"GET",
"https://data.wa.gov/api/views/f6w7-q2d2/rows.csv?accessType=DOWNLOAD",
preload_content=False
)
total_size = int(response.headers.get('Content-Length', 0))
downloaded = 0
progress_bar = st.progress(0)
with open(filepath, "wb") as out:
for chunk in response.stream(4096):
out.write(chunk)
downloaded += len(chunk)
if total_size > 0:
progress_bar.progress(downloaded / total_size)
response.release_conn()
progress_bar.empty()
df = pl.scan_csv(filepath)
Pots executar la consulta següent:
query = (
df.filter((pl.col("Model Year") >= 2018))
.filter(pl.col("Electric Vehicle Type") == "Battery Electric Vehicle (BEV)")
.group_by(["State", "Make"])
.agg(
pl.mean("Electric Range").alias("Average Electric Range"),
pl.min("Model Year").alias("Oldest Model Year"),
pl.len().alias("Number of Cars"),
)
.filter(pl.col("Average Electric Range") > 0)
.filter(pl.col("Number of Cars") > 5)
.sort(pl.col("Number of Cars"), descending=True)
)
print(query.collect())
En aquesta consulta:
- Filtres les dades de tots els cotxes on l’any del model és 2018 o posterior i el tipus de vehicle elèctric és Vehicle Elèctric de Bateria (BEV).
- A continuació calcules el rang elèctric mitjà, l’any del model més antic i el nombre de cotxes per a cada estat i marca.
- Finalment, filtres més les dades on el rang elèctric mitjà és positiu i on el nombre de cotxes per a l’estat i la marca és superior a cinc.
shape: (22, 5)
┌───────┬───────────┬────────────────────────┬───────────────────┬────────────────┐
│ State ┆ Make ┆ Average Electric Range ┆ Oldest Model Year ┆ Number of Cars │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ i64 ┆ u32 │
╞═══════╪═══════════╪════════════════════════╪═══════════════════╪════════════════╡
│ WA ┆ TESLA ┆ 56.715893 ┆ 2018 ┆ 85408 │
│ WA ┆ CHEVROLET ┆ 88.73235 ┆ 2018 ┆ 8881 │
│ WA ┆ NISSAN ┆ 67.008487 ┆ 2018 ┆ 7423 │
│ WA ┆ FORD ┆ 0.083241 ┆ 2018 ┆ 7208 │
│ WA ┆ KIA ┆ 35.681039 ┆ 2018 ┆ 6239 │
│ … ┆ … ┆ … ┆ … ┆ … │
│ MD ┆ TESLA ┆ 33.733333 ┆ 2018 ┆ 15 │
│ TX ┆ TESLA ┆ 105.785714 ┆ 2018 ┆ 14 │
│ NC ┆ TESLA ┆ 16.538462 ┆ 2018 ┆ 13 │
│ FL ┆ TESLA ┆ 63.875 ┆ 2019 ┆ 8 │
│ CO ┆ TESLA ┆ 35.833333 ┆ 2018 ┆ 6 │
└───────┴───────────┴────────────────────────┴───────────────────┴────────────────┘
Mapa de cultius
Per la part pràctica has d’utilitzar un CSV que té les dades de l’any 2016 de tots els polígons dels cultius declarats en la declaració única agrària (DUN), declaració anual de l’explotació agrària, que de forma obligatòria ha de fer la persona titular que disposa de superfície productiva agrícola (excloses les de consum propi), i vol sol·licitar determinats ajuts o hagi de realitzar determinats tràmits amb el DARP.
Aquestes dades s’obté fusionant els recintes SIGPAC d’un mateix producte/conreu declarat en la DUN, i el mateix sistema d’explotació (secà/regadiu) per a cada municipi. És un fitxer CSV de 958 MB que has de descarregar mitjançant codi (i no ha de formar part de l’entrega!)
En aquest enllaç tens les dades que necessites: Mapa de cultivos de Cataluña con origen DUN 2016
Has de crear una aplicació que mostri diferents dades dels cultius utilitzant els gràfics corresponents.