Parte 2 polars
Expresiones polares en porfundidad¶
En la introducción anterior, si no sabes cómo empezar te recomiendo leerlo, o empieza por Pandas
Hablamos también de que el uso de funciones personalizadas de Python eliminaba la paralelización y que podemos usar las expresiones de la API para mitigar esto
.
Veámos que es lo que eso signifíca.
Comencemos con el conjunto de datos del congreso de EE. UU.
import polars as pl
# Lee el archivo CSV
df = pl.read_csv('../data/legislators-current.csv')
# Muestra las primeras filas del DataFrame
print(df.describe)
print(df.columns)
<bound method DataFrame.describe of shape: (541, 36) ┌───────────┬────────────┬────────────┬────────┬───┬────────────┬───────────┬──────────┬───────────┐ │ last_name ┆ first_name ┆ middle_nam ┆ suffix ┆ … ┆ ballotpedi ┆ washingto ┆ icpsr_id ┆ wikipedia │ │ --- ┆ --- ┆ e ┆ --- ┆ ┆ a_id ┆ n_post_id ┆ --- ┆ _id │ │ str ┆ str ┆ --- ┆ str ┆ ┆ --- ┆ --- ┆ i64 ┆ --- │ │ ┆ ┆ str ┆ ┆ ┆ str ┆ str ┆ ┆ str │ ╞═══════════╪════════════╪════════════╪════════╪═══╪════════════╪═══════════╪══════════╪═══════════╡ │ Brown ┆ Sherrod ┆ null ┆ null ┆ … ┆ Sherrod ┆ null ┆ 29389 ┆ Sherrod │ │ ┆ ┆ ┆ ┆ ┆ Brown ┆ ┆ ┆ Brown │ │ Cantwell ┆ Maria ┆ null ┆ null ┆ … ┆ Maria ┆ null ┆ 39310 ┆ Maria │ │ ┆ ┆ ┆ ┆ ┆ Cantwell ┆ ┆ ┆ Cantwell │ │ Cardin ┆ Benjamin ┆ L. ┆ null ┆ … ┆ Ben Cardin ┆ null ┆ 15408 ┆ Ben │ │ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Cardin │ │ Carper ┆ Thomas ┆ Richard ┆ null ┆ … ┆ Tom Carper ┆ null ┆ 15015 ┆ Tom │ │ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ Carper │ │ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │ │ Van Orden ┆ Derrick ┆ Francis ┆ null ┆ … ┆ Derrick ┆ null ┆ null ┆ Derrick │ │ ┆ ┆ ┆ ┆ ┆ Van Orden ┆ ┆ ┆ Van Orden │ │ Hageman ┆ Harriet ┆ Maxine ┆ null ┆ … ┆ Harriet ┆ null ┆ null ┆ Harriet │ │ ┆ ┆ ┆ ┆ ┆ Hageman ┆ ┆ ┆ Hageman │ │ Ricketts ┆ Pete ┆ null ┆ null ┆ … ┆ Pete ┆ null ┆ null ┆ Pete │ │ ┆ ┆ ┆ ┆ ┆ Ricketts ┆ ┆ ┆ Ricketts │ │ McClellan ┆ Jennifer ┆ null ┆ null ┆ … ┆ Jennifer ┆ null ┆ null ┆ Jennifer │ │ ┆ ┆ ┆ ┆ ┆ McClellan ┆ ┆ ┆ McClellan │ └───────────┴────────────┴────────────┴────────┴───┴────────────┴───────────┴──────────┴───────────┘> ['last_name', 'first_name', 'middle_name', 'suffix', 'nickname', 'full_name', 'birthday', 'gender', 'type', 'state', 'district', 'senate_class', 'party', 'url', 'address', 'phone', 'contact_form', 'rss_url', 'twitter', 'twitter_id', 'facebook', 'youtube', 'youtube_id', 'mastodon', 'bioguide_id', 'thomas_id', 'opensecrets_id', 'lis_id', 'fec_ids', 'cspan_id', 'govtrack_id', 'votesmart_id', 'ballotpedia_id', 'washington_post_id', 'icpsr_id', 'wikipedia_id']
df = pl.DataFrame(
{
"a": [1, 2, 3, 4],
"b": [0.5, 4, 10, 13],
"c": [True, True, False, True],
}
)
df.with_columns((pl.col("a") ** 2).alias("a^2"))
a | b | c | a^2 |
---|---|---|---|
i64 | f64 | bool | f64 |
1 | 0.5 | true | 1.0 |
2 | 4.0 | true | 4.0 |
3 | 10.0 | false | 9.0 |
4 | 13.0 | true | 16.0 |
dataset = pl.read_csv("legislators-current.csv")
print(dataset.head())
Agregaciones básicas¶
Puede combinar fácilmente diferentes agregaciones agregando varias expresiones en una lista. No hay un límite superior en el número de agregaciones que puede hacer y puede hacer cualquier combinación que desee. En el fragmento a continuación, hacemos las siguientes agregaciones:
Por grupo "first_name"
:
- cuente el número de filas en el grupo:
- forma abreviada:
pl.count("party")
- forma completa:
pl.col("party").count()
- forma abreviada:
- agregue el grupo de valores de género a una lista:
- forma completa:
pl.col("gender").list()
- forma completa:
- obtenga el primer valor de la columna
"last_name"
en el grupo:- forma abreviada:
pl.primero("last_name")
- forma completa:
pl.col("last_name").first()
- forma abreviada:
Además de la agregación, clasificamos inmediatamente el resultado y lo limitamos a los 5 principales para que tengamos un buen resumen general. q = ( dataset.lazy() .groupby("first_name") .agg( [ pl.count(), pl.col("gender").list(), pl.first("last_name"), ] ) .sort("count", reverse=True) .limit(5) )
df = q.collect() print(df)
pl.col("party").count()
Condicionales¶
Ok, es fué muy fácil, ¿verdad? Next level. Digamos que queremos saber cuántos legisladores de un "estado" (state
) son del partido "Democrat" o "Republican". Podríamos consultarlo directamente en la agregación sin la necesidad de lambda
o arreglar el DataFrame.
q = (
dataset.lazy()
.groupby("state")
.agg(
[
(pl.col("party") == "Democrat").sum().alias("demo"),
(pl.col("party") == "Republican").sum().alias("repu"),
]
)
.sort("demo", reverse=True)
.limit(5)
)
df
print(df)
✅Por supuesto, también se podría hacer algo similar con un
GROUPBY
anidado, pero eso no me permitiría mostrar estas características agradables.
q = (
dataset.lazy()
.groupby(["state", "party"])
.agg([pl.count("party").alias("count")])
.filter((pl.col("party") == "Democrat") | (pl.col("party") == "Republican"))
.sort("count", reverse=True)
.limit(5)
)
df = q.collect()
print(df)
Filtración de datos¶
También podemos filtrar los grupos. Digamos que queremos calcular una media por grupo, pero no queremos incluir todos los valores de ese grupo y tampoco queremos filtrar las filas del DataFrame
(porque necesitamos esas filas para otra agregación).
En el siguiente ejemplo, mostramos cómo se puede hacer eso. Tenga en cuenta que podemos hacer funciones de Python para mayor claridad. Estas funciones no nos cuestan nada. Esto se debe a que solo creamos Polars expression
, no aplicamos una función personalizada sobre Series
durante el tiempo de ejecución de la consulta.
from datetime import date
def compute_age() -> pl.Expr:
return date(2021, 1, 1).year - pl.col("birthday").dt.year()
def avg_birthday(gender: str) -> pl.Expr:
return compute_age().filter(pl.col("gender") == gender).mean().alias(f"avg {gender} birthday")
q = (
dataset.lazy()
.groupby(["state"])
.agg(
[
avg_birthday("M"),
avg_birthday("F"),
(pl.col("gender") == "M").sum().alias("# male"),
(pl.col("gender") == "F").sum().alias("# female"),
]
)
.limit(5)
)
df = q.collect()
print(df)
Sorting | Ordenación¶
A menudo veo que se ordena un DataFrame con el único propósito de ordenar durante la operación GROUPBY
. Digamos que queremos obtener los nombres de los políticos más antiguos y más jóvenes (no es que todavía estén vivos) por estado, podríamos ORDENAR
y AGRUPAR
.
def get_person() -> pl.Expr:
return pl.col("first_name") + pl.lit(" ") + pl.col("last_name")
q = (
dataset.lazy()
.sort("birthday")
.groupby(["state"])
.agg(
[
get_person().first().alias("youngest"),
get_person().last().alias("oldest"),
]
)
.limit(5)
)
df = q.collect()
print(df)