Introducció
Operacions bàsiques
Utilitzarem el dataframe següent per als exemples que segueixen:
import polars as pl
import numpy as np
np.random.seed(42) # For reproducibility.
df = pl.DataFrame(
{
"nrs": [1, 2, 3, None, 5],
"names": ["foo", "ham", "spam", "egg", "spam"],
"random": np.random.rand(5),
"groups": ["A", "A", "B", "A", "B"],
}
)
print(df)
shape: (5, 4)
┌──────┬───────┬──────────┬────────┐
│ nrs ┆ names ┆ random ┆ groups │
│ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ str ┆ f64 ┆ str │
╞══════╪═══════╪══════════╪════════╡
│ 1 ┆ foo ┆ 0.37454 ┆ A │
│ 2 ┆ ham ┆ 0.950714 ┆ A │
│ 3 ┆ spam ┆ 0.731994 ┆ B │
│ null ┆ egg ┆ 0.598658 ┆ A │
│ 5 ┆ spam ┆ 0.156019 ┆ B │
└──────┴───────┴──────────┴────────┘
Aritmètica bàsica
Polars admet aritmètica bàsica entre sèries de la mateixa longitud, o entre sèries i literals.
Quan es barregen literals amb sèries, els literals es difonen (broadcast) per igualar la longitud de la sèrie amb la qual s’utilitzen.
result = df.select(
(pl.col("nrs") + 5).alias("nrs + 5"),
(pl.col("nrs") - 5).alias("nrs - 5"),
(pl.col("nrs") * pl.col("random")).alias("nrs * random"),
(pl.col("nrs") / pl.col("random")).alias("nrs / random"),
(pl.col("nrs") ** 2).alias("nrs ** 2"),
(pl.col("nrs") % 3).alias("nrs % 3"),
)
print(result)
shape: (5, 6)
┌─────────┬─────────┬──────────────┬──────────────┬──────────┬─────────┐
│ nrs + 5 ┆ nrs - 5 ┆ nrs * random ┆ nrs / random ┆ nrs ** 2 ┆ nrs % 3 │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ f64 ┆ f64 ┆ i64 ┆ i64 │
╞═════════╪═════════╪══════════════╪══════════════╪══════════╪═════════╡
│ 6 ┆ -4 ┆ 0.37454 ┆ 2.669941 ┆ 1 ┆ 1 │
│ 7 ┆ -3 ┆ 1.901429 ┆ 2.103681 ┆ 4 ┆ 2 │
│ 8 ┆ -2 ┆ 2.195982 ┆ 4.098395 ┆ 9 ┆ 0 │
│ null ┆ null ┆ null ┆ null ┆ null ┆ null │
│ 10 ┆ 0 ┆ 0.780093 ┆ 32.047453 ┆ 25 ┆ 2 │
└─────────┴─────────┴──────────────┴──────────────┴──────────┴─────────┘
L’exemple anterior mostra que quan una operació aritmètica rep null
com un dels operands, el resultat és null
.
Polars utilitza la sobrecàrrega d’operadors per permetre’t usar els operadors aritmètics natius del teu llenguatge dins de les expressions.
Si ho prefereixes, en Python pots utilitzar les funcions amb nom corresponents, tal com mostra el fragment següent:
result_named_operators = df.select(
(pl.col("nrs").add(5)).alias("nrs + 5"),
(pl.col("nrs").sub(5)).alias("nrs - 5"),
(pl.col("nrs").mul(pl.col("random"))).alias("nrs * random"),
(pl.col("nrs").truediv(pl.col("random"))).alias("nrs / random"),
(pl.col("nrs").pow(2)).alias("nrs ** 2"),
(pl.col("nrs").mod(3)).alias("nrs % 3"),
)
print(result.equals(result_named_operators))
True
Comparacions
Com amb les operacions aritmètiques, Polars admet comparacions mitjançant operadors sobrecarregats o funcions amb nom:
result = df.select(
(pl.col("nrs") > 1).alias("nrs > 1"), # .gt
(pl.col("nrs") >= 3).alias("nrs >= 3"), # ge
(pl.col("random") < 0.2).alias("random < .2"), # .lt
(pl.col("random") <= 0.5).alias("random <= .5"), # .le
(pl.col("nrs") != 1).alias("nrs != 1"), # .ne
(pl.col("nrs") == 1).alias("nrs == 1"), # .eq
)
print(result)
shape: (5, 6)
┌─────────┬──────────┬─────────────┬──────────────┬──────────┬──────────┐
│ nrs > 1 ┆ nrs >= 3 ┆ random < .2 ┆ random <= .5 ┆ nrs != 1 ┆ nrs == 1 │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ bool ┆ bool ┆ bool ┆ bool ┆ bool ┆ bool │
╞═════════╪══════════╪═════════════╪══════════════╪══════════╪══════════╡
│ false ┆ false ┆ false ┆ true ┆ false ┆ true │
│ true ┆ false ┆ false ┆ false ┆ true ┆ false │
│ true ┆ true ┆ false ┆ false ┆ true ┆ false │
│ null ┆ null ┆ false ┆ false ┆ null ┆ null │
│ true ┆ true ┆ true ┆ true ┆ true ┆ false │
└─────────┴──────────┴─────────────┴──────────────┴──────────┴──────────┘
Operacions booleanes i bit a bit
Segons el llenguatge, pots utilitzar els operadors &, | i ~ per a les operacions booleanes “and”, “or” i “not”, respectivament, o les funcions del mateix nom:
# Boolean operators & | ~
result = df.select(
((~pl.col("nrs").is_null()) & (pl.col("groups") == "A")).alias(
"number not null and group A"
),
((pl.col("random") < 0.5) | (pl.col("groups") == "B")).alias(
"random < 0.5 or group B"
),
)
print(result)
# Corresponding named functions `and_`, `or_`, and `not_`.
result2 = df.select(
(pl.col("nrs").is_null().not_().and_(pl.col("groups") == "A")).alias(
"number not null and group A"
),
((pl.col("random") < 0.5).or_(pl.col("groups") == "B")).alias(
"random < 0.5 or group B"
),
)
print(result.equals(result2))
shape: (5, 2)
┌─────────────────────────────┬─────────────────────────┐
│ number not null and group A ┆ random < 0.5 or group B │
│ --- ┆ --- │
│ bool ┆ bool │
╞═════════════════════════════╪═════════════════════════╡
│ true ┆ true │
│ true ┆ false │
│ false ┆ true │
│ false ┆ false │
│ false ┆ true │
└─────────────────────────────┴─────────────────────────┘
True
Aquests operadors/funcions també es poden utilitzar per a les respectives operacions bit a bit, juntament amb l’operador bit a bit ^
/ la funció xor
:
result = df.select(
pl.col("nrs"),
(pl.col("nrs") & 6).alias("nrs & 6"),
(pl.col("nrs") | 6).alias("nrs | 6"),
(~pl.col("nrs")).alias("not nrs"),
(pl.col("nrs") ^ 6).alias("nrs ^ 6"),
)
print(result)
shape: (5, 5)
┌──────┬─────────┬─────────┬─────────┬─────────┐
│ nrs ┆ nrs & 6 ┆ nrs | 6 ┆ not nrs ┆ nrs ^ 6 │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 │
╞══════╪═════════╪═════════╪═════════╪═════════╡
│ 1 ┆ 0 ┆ 7 ┆ -2 ┆ 7 │
│ 2 ┆ 2 ┆ 6 ┆ -3 ┆ 4 │
│ 3 ┆ 2 ┆ 7 ┆ -4 ┆ 5 │
│ null ┆ null ┆ null ┆ null ┆ null │
│ 5 ┆ 4 ┆ 7 ┆ -6 ┆ 3 │
└──────┴─────────┴─────────┴─────────┴─────────┘
Comptar valors (únics)
Polars té dues funcions per comptar el nombre de valors únics en una sèrie.
La funció n_unique
es pot utilitzar per comptar el nombre exacte de valors únics en una sèrie.
Tanmateix, per a conjunts de dades molt grans, aquesta operació pot ser força lenta. En aquests casos, si una aproximació és suficient, pots utilitzar la funció approx_n_unique
, que utilitza l’algoritme HyperLogLog++ per estimar el resultat.
L’exemple següent mostra una sèrie on l’estimació d’approx_n_unique
s’equivoca en un 0,9%:
long_df = pl.DataFrame({"numbers": np.random.randint(0, 100_000, 100_000)})
result = long_df.select(
pl.col("numbers").n_unique().alias("n_unique"),
pl.col("numbers").approx_n_unique().alias("approx_n_unique"),
)
print(result)
shape: (1, 2)
┌──────────┬─────────────────┐
│ n_unique ┆ approx_n_unique │
│ --- ┆ --- │
│ u64 ┆ u64 │
╞══════════╪═════════════════╡
│ 63218 ┆ 64141 │
└──────────┴─────────────────┘
Pots obtenir més informació sobre els valors únics i els seus recomptes amb la funció value_counts
, que Polars també proporciona:
result = df.select(
pl.col("names").value_counts().alias("value_counts"),
)
print(result)
shape: (4, 1)
┌──────────────┐
│ value_counts │
│ --- │
│ struct[2] │
╞══════════════╡
│ {"ham",1} │
│ {"spam",2} │
│ {"foo",1} │
│ {"egg",1} │
└──────────────┘
La funció value_counts
retorna els resultats en structs, un tipus de dada que explorarem en una secció posterior.
Alternativament, si només necessites una sèrie amb els valors únics o una sèrie amb els recomptes únics, ho tens a una funció de distància:
result = df.select(
pl.col("names").unique(maintain_order=True).alias("unique"),
pl.col("names").unique_counts().alias("unique_counts"),
)
print(result)
shape: (4, 2)
┌────────┬───────────────┐
│ unique ┆ unique_counts │
│ --- ┆ --- │
│ str ┆ u64 │
╞════════╪═══════════════╡
│ foo ┆ 1 │
│ ham ┆ 1 │
│ spam ┆ 2 │
│ egg ┆ 1 │
└────────┴───────────────┘
Tingues en compte que hem d’especificar maintain_order=True
a la funció unique
perquè l’ordre dels resultats sigui consistent amb l’ordre dels resultats a unique_counts
.
Consulta la referència de l’API per a més informació.
Condicionals
Polars admet una funcionalitat semblant a un operador ternari mitjançant la funció when
, que va seguida d’una funció then
i una funció opcional otherwise
.
La funció when
accepta una expressió de predicat. Els valors que s’avaluen com a True
se substitueixen pels valors corresponents de l’expressió dins de la funció then
. Els valors que s’avaluen com a False
se substitueixen pels valors corresponents de l’expressió dins de la funció otherwise
o per null
si no es proporciona otherwise
.
L’exemple següent aplica un pas de la conjectura de Collatz als nombres de la columna “nrs”:
result = df.select(
pl.col("nrs"),
pl.when(pl.col("nrs") % 2 == 1) # Is the number odd?
.then(3 * pl.col("nrs") + 1) # If so, multiply by 3 and add 1.
.otherwise(pl.col("nrs") // 2) # If not, divide by 2.
.alias("Collatz"),
)
print(result)
shape: (5, 2)
┌──────┬─────────┐
│ nrs ┆ Collatz │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞══════╪═════════╡
│ 1 ┆ 4 │
│ 2 ┆ 1 │
│ 3 ┆ 10 │
│ null ┆ null │
│ 5 ┆ 16 │
└──────┴─────────┘
També pots emular una cadena d’un nombre arbitrari de condicionals, semblant a l’instrucció elif
de Python, encadenant un nombre arbitrari de blocs consecutius de .when(...).then(...)
.
En aquests casos, i per a cada valor donat, Polars només considerarà una expressió de substitució més profunda a la cadena si tots els predicats anteriors han fallat per a aquell valor.
Expansió d’expressions
L’expansió d’expressions és una funcionalitat que et permet escriure una única expressió que es pot expandir en múltiples expressions diferents, possiblement depenent de l’esquema del context en què s’utilitza l’expressió.
Aquesta funcionalitat no és només decorativa ni sucre sintàctic. Permet aplicar de manera molt potent els principis DRY al teu codi: una sola expressió que especifica múltiples columnes s’expandeix en una llista d’expressions, la qual cosa significa que pots escriure una sola expressió i reutilitzar el càlcul que representa.
En aquesta secció mostrarem diverses formes d’expansió d’expressions i farem servir el dataframe següent per a aquest efecte:
import polars as pl
df = pl.DataFrame(
{ # As of 14th October 2024, ~3pm UTC
"ticker": ["AAPL", "NVDA", "MSFT", "GOOG", "AMZN"],
"company_name": ["Apple", "NVIDIA", "Microsoft", "Alphabet (Google)", "Amazon"],
"price": [229.9, 138.93, 420.56, 166.41, 188.4],
"day_high": [231.31, 139.6, 424.04, 167.62, 189.83],
"day_low": [228.6, 136.3, 417.52, 164.78, 188.44],
"year_high": [237.23, 140.76, 468.35, 193.31, 201.2],
"year_low": [164.08, 39.23, 324.39, 121.46, 118.35],
}
)
print(df)
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ Alphabet (Google) ┆ 166.41 ┆ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
Funció col
La funció col
és la manera més habitual d’utilitzar les capacitats d’expansió d’expressions a Polars.
Normalment, s’utilitza per referir-se a una columna del dataframe; en aquesta secció explorem altres maneres d’usar col
.
Expansió explícita per nom de columna
La forma més simple d’expansió d’expressions passa quan proporciones múltiples noms de columna a la funció col
.
L’exemple següent utilitza una única funció col
amb diversos noms de columna per convertir els valors de USD a EUR:
eur_usd_rate = 1.09 # As of 14th October 2024
result = df.with_columns(
(
pl.col(
"price",
"day_high",
"day_low",
"year_high",
"year_low",
)
/ eur_usd_rate
).round(2)
)
print(result)
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 210.92 ┆ 212.21 ┆ 209.72 ┆ 217.64 ┆ 150.53 │
│ NVDA ┆ NVIDIA ┆ 127.46 ┆ 128.07 ┆ 125.05 ┆ 129.14 ┆ 35.99 │
│ MSFT ┆ Microsoft ┆ 385.83 ┆ 389.03 ┆ 383.05 ┆ 429.68 ┆ 297.61 │
│ GOOG ┆ Alphabet (Google) ┆ 152.67 ┆ 153.78 ┆ 151.17 ┆ 177.35 ┆ 111.43 │
│ AMZN ┆ Amazon ┆ 172.84 ┆ 174.16 ┆ 172.88 ┆ 184.59 ┆ 108.58 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
Quan llistes els noms de columna als quals vols que s’expandeixi l’expressió, pots predir en què s’expandirà.
En aquest cas, l’expressió que fa la conversió de moneda s’expandeix en una llista de cinc expressions:
exprs = [
(pl.col("price") / eur_usd_rate).round(2),
(pl.col("day_high") / eur_usd_rate).round(2),
(pl.col("day_low") / eur_usd_rate).round(2),
(pl.col("year_high") / eur_usd_rate).round(2),
(pl.col("year_low") / eur_usd_rate).round(2),
]
result2 = df.with_columns(exprs)
print(result.equals(result2))
True
Expansió per tipus de dada
Al fragment anterior hem hagut d’escriure cinc noms de columna, però la funció col
també pot acceptar convenientment un o més tipus de dada. Si proporciones tipus de dada en lloc de noms de columna, l’expressió s’expandeix a totes les columnes que coincideixin amb algun dels tipus proporcionats.
L’exemple següent fa exactament el mateix càlcul que abans:
result = df.with_columns((pl.col(pl.Float64) / eur_usd_rate).round(2))
print(result)
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 210.92 ┆ 212.21 ┆ 209.72 ┆ 217.64 ┆ 150.53 │
│ NVDA ┆ NVIDIA ┆ 127.46 ┆ 128.07 ┆ 125.05 ┆ 129.14 ┆ 35.99 │
│ MSFT ┆ Microsoft ┆ 385.83 ┆ 389.03 ┆ 383.05 ┆ 429.68 ┆ 297.61 │
│ GOOG ┆ Alphabet (Google) ┆ 152.67 ┆ 153.78 ┆ 151.17 ┆ 177.35 ┆ 111.43 │
│ AMZN ┆ Amazon ┆ 172.84 ┆ 174.16 ┆ 172.88 ┆ 184.59 ┆ 108.58 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
Quan fem servir un tipus de dada amb expansió d’expressions no podem saber, per endavant, a quantes columnes s’expandirà una sola expressió. Necessitem l’esquema del dataframe d’entrada si volem determinar quina és la llista final d’expressions a aplicar.
Si no estas segur de si les columnes de preu són del tipus Float64
o Float32
, pots especificar tots dos tipus:
result2 = df.with_columns(
(
pl.col(
pl.Float32,
pl.Float64,
)
/ eur_usd_rate
).round(2)
)
print(result.equals(result2))
True
Expansió per concordança de patrons
També pots usar expressions regulars per indicar patrons que es fan servir per fer concordar els noms de columna.
Per distingir entre un nom de columna normal i una expansió per concordança de patrons, les expressions regulars comencen amb ^
i acaben amb $
, respectivament. Això també implica que el patró ha de concordar amb tot el nom de la columna.
Les expressions regulars es poden barrejar amb noms de columna normals:
result = df.select(pl.col("ticker", "^.*_high$", "^.*_low$"))
print(result)
shape: (5, 5)
┌────────┬──────────┬─────────┬───────────┬──────────┐
│ ticker ┆ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└────────┴──────────┴─────────┴───────────┴──────────┘
No es poden barrejar tipus d’arguments
La funció col
accepta un nombre arbitrari de strings (com a noms de columna o com a expressions regulars) o un nombre arbitrari de tipus de dada, però no pots barrejar tots dos en la mateixa crida:
try:
df.select(pl.col("ticker", pl.Float64))
except TypeError as err:
print("TypeError:", err)
TypeError: argument 'names': 'DataTypeClass' object cannot be converted to 'PyString'
Seleccionar totes les columnes
Polars proporciona la funció all
com a notació abreujada per referir-se a totes les columnes d’un dataframe:
result = df.select(pl.all())
print(result.equals(df))
True
Excloure columnes
Polars també ofereix un mecanisme per excloure certes columnes de l’expansió d’expressions. Per a això, fas servir la funció exclude
, que accepta exactament els mateixos tipus d’arguments que col
:
result = df.select(pl.all().exclude("^day_.*$"))
print(result)
shape: (5, 5)
┌────────┬───────────────────┬────────┬───────────┬──────────┐
│ ticker ┆ company_name ┆ price ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ Alphabet (Google) ┆ 166.41 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 201.2 ┆ 118.35 │
└────────┴───────────────────┴────────┴───────────┴──────────┘
Naturalment, la funció exclude
també es pot utilitzar després de la funció col
:
result = df.select(pl.col(pl.Float64).exclude("^day_.*$"))
print(result)
shape: (5, 3)
┌────────┬───────────┬──────────┐
│ price ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════╪══════════╡
│ 229.9 ┆ 237.23 ┆ 164.08 │
│ 138.93 ┆ 140.76 ┆ 39.23 │
│ 420.56 ┆ 468.35 ┆ 324.39 │
│ 166.41 ┆ 193.31 ┆ 121.46 │
│ 188.4 ┆ 201.2 ┆ 118.35 │
└────────┴───────────┴──────────┘
Canvi de nom de columnes
Per defecte, quan apliques una expressió a una columna, el resultat manté el mateix nom que la columna original.
Preservar el nom de la columna pot ser semànticament incorrecte i, en alguns casos, Polars fins i tot pot generar un error si es produeixen noms duplicats:
from polars.exceptions import DuplicateError
gbp_usd_rate = 1.31 # As of 14th October 2024
try:
df.select(
pl.col("price") / gbp_usd_rate, # This would be named "price"...
pl.col("price") / eur_usd_rate, # And so would this.
)
except DuplicateError as err:
print("DuplicateError:", err)
DuplicateError: the name 'price' is duplicate
It's possible that multiple expressions are returning the same default column name. If this is the case, try renaming the columns with `.alias("new_name")` to avoid duplicate column names.
Per evitar errors com aquest, i per permetre als usuaris canviar el nom de les columnes quan sigui oportú, Polars ofereix una sèrie de funcions que et permeten canviar el nom d’una columna o d’un grup de columnes.
Canviar el nom d’una sola columna amb alias
La funció alias
ja s’ha fet servir molt en les activitats anteriors i et permet reanomenar una sola columna:
result = df.select(
(pl.col("price") / gbp_usd_rate).alias("price (GBP)"),
(pl.col("price") / eur_usd_rate).alias("price (EUR)"),
)
Prefixar i sufixar noms de columnes
Quan utilitzes l’expansió d’expressions no pots fer servir la funció alias perquè la funció alias està dissenyada específicament per canviar el nom d’una sola columna.
Quan n’hi ha prou amb afegir un prefix estàtic o un sufix estàtic als noms existents, podem utilitzar les funcions prefix i suffix de l’espai de noms name
:
result = df.select(
(pl.col("^year_.*$") / eur_usd_rate).name.prefix("in_eur_"),
(pl.col("day_high", "day_low") / gbp_usd_rate).name.suffix("_gbp"),
)
print(result)
shape: (5, 4)
┌──────────────────┬─────────────────┬──────────────┬─────────────┐
│ in_eur_year_high ┆ in_eur_year_low ┆ day_high_gbp ┆ day_low_gbp │
│ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 │
╞══════════════════╪═════════════════╪══════════════╪═════════════╡
│ 217.642202 ┆ 150.53211 ┆ 176.572519 ┆ 174.503817 │
│ 129.137615 ┆ 35.990826 ┆ 106.564885 ┆ 104.045802 │
│ 429.678899 ┆ 297.605505 ┆ 323.694656 ┆ 318.717557 │
│ 177.348624 ┆ 111.431193 ┆ 127.954198 ┆ 125.78626 │
│ 184.587156 ┆ 108.577982 ┆ 144.908397 ┆ 143.847328 │
└──────────────────┴─────────────────┴──────────────┴─────────────┘
Substitució dinàmica de noms
Si un prefix/sufix estàtic no és suficient, l’espai de noms name
també proporciona la funció map
que accepta un callable que accepta els noms de columna antics i produeix els nous:
# There is also `.name.to_uppercase`, so this usage of `.map` is moot.
result = df.select(pl.all().name.map(str.upper))
print(result)
shape: (5, 7)
┌────────┬───────────────────┬────────┬──────────┬─────────┬───────────┬──────────┐
│ TICKER ┆ COMPANY_NAME ┆ PRICE ┆ DAY_HIGH ┆ DAY_LOW ┆ YEAR_HIGH ┆ YEAR_LOW │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪════════╪══════════╪═════════╪═══════════╪══════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ GOOG ┆ Alphabet (Google) ┆ 166.41 ┆ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└────────┴───────────────────┴────────┴──────────┴─────────┴───────────┴──────────┘
Generació programàtica d’expressions
L’expansió d’expressions és una funcionalitat molt útil, però no resol tots els teus problemes.
Per exemple, si vols calcular l’amplitud del dia i de l’any dels preus de les accions del nostre dataframe, l’expansió d’expressions no ens ajudarà.
En un primer moment, pots pensar a utilitzar un bucle for
:
result = df
for tp in ["day", "year"]:
result = result.with_columns(
(pl.col(f"{tp}_high") - pl.col(f"{tp}_low")).alias(f"{tp}_amplitude")
)
print(result)
shape: (5, 9)
┌────────┬──────────────┬────────┬──────────┬───┬───────────┬──────────┬─────────────┬─────────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ … ┆ year_high ┆ year_low ┆ day_amplitu ┆ year_amplit │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ de ┆ ude │
│ str ┆ str ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ --- ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ f64 ┆ f64 │
╞════════╪══════════════╪════════╪══════════╪═══╪═══════════╪══════════╪═════════════╪═════════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ … ┆ 237.23 ┆ 164.08 ┆ 2.71 ┆ 73.15 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ … ┆ 140.76 ┆ 39.23 ┆ 3.3 ┆ 101.53 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ … ┆ 468.35 ┆ 324.39 ┆ 6.52 ┆ 143.96 │
│ GOOG ┆ Alphabet ┆ 166.41 ┆ 167.62 ┆ … ┆ 193.31 ┆ 121.46 ┆ 2.84 ┆ 71.85 │
│ ┆ (Google) ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ … ┆ 201.2 ┆ 118.35 ┆ 1.39 ┆ 82.85 │
└────────┴──────────────┴────────┴──────────┴───┴───────────┴──────────┴─────────────┴─────────────┘
No facis això. En lloc d’això, genera totes les expressions que vols calcular programàticament i utilitza-les només una vegada en un context.
En termes generals, vols intercanviar el bucle for
amb el context with_columns
.
A la pràctica, pots fer alguna cosa com el següent:
def amplitude_expressions(time_periods):
for tp in time_periods:
yield (pl.col(f"{tp}_high") - pl.col(f"{tp}_low")).alias(f"{tp}_amplitude")
result = df.with_columns(amplitude_expressions(["day", "year"]))
print(result)
shape: (5, 9)
┌────────┬──────────────┬────────┬──────────┬───┬───────────┬──────────┬─────────────┬─────────────┐
│ ticker ┆ company_name ┆ price ┆ day_high ┆ … ┆ year_high ┆ year_low ┆ day_amplitu ┆ year_amplit │
│ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ --- ┆ de ┆ ude │
│ str ┆ str ┆ f64 ┆ f64 ┆ ┆ f64 ┆ f64 ┆ --- ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ ┆ ┆ f64 ┆ f64 │
╞════════╪══════════════╪════════╪══════════╪═══╪═══════════╪══════════╪═════════════╪═════════════╡
│ AAPL ┆ Apple ┆ 229.9 ┆ 231.31 ┆ … ┆ 237.23 ┆ 164.08 ┆ 2.71 ┆ 73.15 │
│ NVDA ┆ NVIDIA ┆ 138.93 ┆ 139.6 ┆ … ┆ 140.76 ┆ 39.23 ┆ 3.3 ┆ 101.53 │
│ MSFT ┆ Microsoft ┆ 420.56 ┆ 424.04 ┆ … ┆ 468.35 ┆ 324.39 ┆ 6.52 ┆ 143.96 │
│ GOOG ┆ Alphabet ┆ 166.41 ┆ 167.62 ┆ … ┆ 193.31 ┆ 121.46 ┆ 2.84 ┆ 71.85 │
│ ┆ (Google) ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ AMZN ┆ Amazon ┆ 188.4 ┆ 189.83 ┆ … ┆ 201.2 ┆ 118.35 ┆ 1.39 ┆ 82.85 │
└────────┴──────────────┴────────┴──────────┴───┴───────────┴──────────┴─────────────┴─────────────┘
Això produeix el mateix resultat final i especificant totes les expressions d’un sol cop donem a Polars l’oportunitat de:
- fer una millor feina optimitzant la consulta; i
- paral·lelitzar l’execució dels càlculs reals.
Seleccions de columnes de manera més flexible
Polars ve amb el submòdul selectors
que proporciona diverses funcions que et permeten escriure seleccions de columnes més flexibles per a l’expansió d’expressions.
Com a primer exemple, aquí tens com podem utilitzar les funcions string
i ends_with
, i les operacions de conjunts que les funcions de selectors
admeten, per seleccionar totes les columnes de tipus string i les columnes els noms de les quals acaben amb “_high”:
import polars.selectors as cs
result = df.select(cs.string() | cs.ends_with("_high"))
print(result)
shape: (5, 4)
┌────────┬───────────────────┬──────────┬───────────┐
│ ticker ┆ company_name ┆ day_high ┆ year_high │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ f64 │
╞════════╪═══════════════════╪══════════╪═══════════╡
│ AAPL ┆ Apple ┆ 231.31 ┆ 237.23 │
│ NVDA ┆ NVIDIA ┆ 139.6 ┆ 140.76 │
│ MSFT ┆ Microsoft ┆ 424.04 ┆ 468.35 │
│ GOOG ┆ Alphabet (Google) ┆ 167.62 ┆ 193.31 │
│ AMZN ┆ Amazon ┆ 189.83 ┆ 201.2 │
└────────┴───────────────────┴──────────┴───────────┘
El submòdul selectors
proporciona diversos selectors que fan coincidències basant-se en el tipus de dada de les columnes, dels quals els més útils són les funcions que fan coincidir tota una categoria de tipus, com cs.numeric
per a tots els tipus de dades numèriques o cs.temporal
per a tots els tipus de dades temporals.
El submòdul selectors
també proporciona diversos selectors que fan coincidències basant-se en patrons dels noms de les columnes, cosa que facilita especificar patrons comuns que potser vols comprovar, com la funció cs.ends_with
que s’ha mostrat anteriorment.
Combinar selectors amb operacions de conjunts
Pots combinar múltiples selectors utilitzant operacions de conjunts i els operadors habituals de Python:
Operator | Operation |
---|---|
A | B | Union |
A & B | Intersection |
A - B | Difference |
A ^ B | Symmetric difference |
~A | Complement |
El següent exemple fa coincidir totes les columnes que no són strings i que contenen un guió baix al nom:
result = df.select(cs.contains("_") - cs.string())
print(result)
shape: (5, 4)
┌──────────┬─────────┬───────────┬──────────┐
│ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 │
╞══════════╪═════════╪═══════════╪══════════╡
│ 231.31 ┆ 228.6 ┆ 237.23 ┆ 164.08 │
│ 139.6 ┆ 136.3 ┆ 140.76 ┆ 39.23 │
│ 424.04 ┆ 417.52 ┆ 468.35 ┆ 324.39 │
│ 167.62 ┆ 164.78 ┆ 193.31 ┆ 121.46 │
│ 189.83 ┆ 188.44 ┆ 201.2 ┆ 118.35 │
└──────────┴─────────┴───────────┴──────────┘
Resoldre l’ambigüitat dels operadors
Les funcions d’expressió es poden encadenar sobre els selectors:
result = df.select((cs.contains("_") - cs.string()) / eur_usd_rate)
print(result)
shape: (5, 4)
┌────────────┬────────────┬────────────┬────────────┐
│ day_high ┆ day_low ┆ year_high ┆ year_low │
│ --- ┆ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════════╪════════════╪════════════╪════════════╡
│ 212.211009 ┆ 209.724771 ┆ 217.642202 ┆ 150.53211 │
│ 128.073394 ┆ 125.045872 ┆ 129.137615 ┆ 35.990826 │
│ 389.027523 ┆ 383.045872 ┆ 429.678899 ┆ 297.605505 │
│ 153.779817 ┆ 151.174312 ┆ 177.348624 ┆ 111.431193 │
│ 174.155963 ┆ 172.880734 ┆ 184.587156 ┆ 108.577982 │
└────────────┴────────────┴────────────┴────────────┘
Tanmateix, alguns operadors s’han sobrecarregat per operar tant sobre els selectors de Polars com sobre les expressions.
Per exemple, l’operador ~
sobre un selector representa l’operació de conjunts “complement” i sobre una expressió representa l’operació booleana de negació.
Quan utilitzes un selector i després vols utilitzar, en el context d’una expressió, un dels operadors que actuen com a operadors de conjunts per als selectors, pots utilitzar la funció as_expr
.
A continuació, volem negar els valors booleans de les columnes “has_partner”, “has_kids” i “has_tattoos”.
Si no anem amb compte, la combinació de l’operador ~
i el selector cs.starts_with("has_")
en realitat seleccionarà les columnes que no ens importen:
people = pl.DataFrame(
{
"name": ["Anna", "Bob"],
"has_partner": [True, False],
"has_kids": [False, False],
"has_tattoos": [True, False],
"is_alive": [True, True],
}
)
wrong_result = people.select((~cs.starts_with("has_")).name.prefix("not_"))
print(wrong_result)
shape: (2, 2)
┌──────────┬──────────────┐
│ not_name ┆ not_is_alive │
│ --- ┆ --- │
│ str ┆ bool │
╞══════════╪══════════════╡
│ Anna ┆ true │
│ Bob ┆ true │
└──────────┴──────────────┘
La solució correcta utilitza as_expr:
result = people.select((~cs.starts_with("has_").as_expr()).name.prefix("not_"))
print(result)
shape: (2, 3)
┌─────────────────┬──────────────┬─────────────────┐
│ not_has_partner ┆ not_has_kids ┆ not_has_tattoos │
│ --- ┆ --- ┆ --- │
│ bool ┆ bool ┆ bool │
╞═════════════════╪══════════════╪═════════════════╡
│ false ┆ true ┆ false │
│ true ┆ true ┆ true │
└─────────────────┴──────────────┴─────────────────┘
Depuració de selectors
Quan no estàs segur de si tens un selector de Polars o no, pots utilitzar la funció cs.is_selector
per comprovar-ho:
print(cs.is_selector(~cs.starts_with("has_").as_expr()))
False
Això t’hauria d’ajudar a evitar situacions ambigües on creus que estàs operant amb expressions però de fet estàs operant amb selectors.
Una altra utilitat de depuració útil és la funció expand_selector
. Donat un marc o esquema objectiu, pots comprovar a quines columnes s’expandirà un selector donat:
print(
cs.expand_selector(
people,
cs.starts_with("has_"),
)
)
('has_partner', 'has_kids', 'has_tattoos')
Referència completa
Les taules següents agrupen les funcions disponibles al submòdul selectors
segons el seu tipus de comportament.
Casting
PENDENT