[PowerQuery] Pandas and SQL to M

En múltiples oportunidades me encontré con personas que se dedican a data engineering o data analytics con python y por requerimiento/necesidad necesitan utilizar herramientas microsoft de movimiento de datos o Power Bi para presentarlos. Lo cierto es que dichas personas suelen detestar ese pedido puesto que esos puestos prefieren un trabajo más back que la demostración de tableros o herramientas de microsoft.

Ciertamente hoy microsoft ha implementado e impulsa Power Query para todo lo que es transformación de datos al punto que puede ser usado en SSIS, SSAS, Power Automate, Power Apps, Power Bi y DataFlows (en Power Platform y Data Factory).

El impulso por el lenguage de transformación es cada vez más fuerte y está pasando de ser un lenguaje de un rol de Bi a roles de data analyst y data engineer.

Por ello cree este post que nos ayudará a conocer como realizar las funciones básicas de SQL y principalmente Pandas (librería de python)

Lo primero a destacar es que Power Query corre en capas. Cada linea llama a la anterior (que devuelve una tabla) generando esta perspectiva o visión en capas. Por ello cuando leamos en el código #"Paso anterior" hablamos de una tabla.

Conozcamos los ejemplos que serán listados en el siguiente orden: SQL, Python, Power Query.

Cinco primeras filas

En SQL:

SELECT TOP 5 * FROM table

En Pandas:

df.head()

En Power Query:

Table.FirstN(#"Paso Anterior",5)

Contar filas

SELECT COUNT(*) FROM table1
df.shape()
Table.RowCount(#"Paso Anterior")

Seleccionar filas

SELECT column1, column2 FROM table1
df[["column1", "column2"]]
#"Paso Anterior"[[Columna1],[Columna2]]

O podría ser:

Table.SelectColumns(#"Paso Anterior", {"Columna1", "Columna2"} )

Filtrar filas

SELECT column1, column2 FROM table1 WHERE column1 = 2
df[['column1', 'column2']].loc[df['column1'] == 2]
Table.SelectRows(#"Paso Anterior", each [column1] == 2 )

Varios filtros de filas

SELECT * FROM table1 WHERE column1 > 1 AND column2 < 25
df.loc[(df['column1'] > 1) & (df['column2'] < 25)]

O con operadores OR y NOT

df.loc[(df['column1'] > 1) | ~(df['column2'] < 25)]
Table.SelectRows(#"Paso Anterior", each [column1] > 1 and column2 < 25 )

O con operadores OR y NOT

Table.SelectRows(#"Paso Anterior", each [column1] > 1 or not ([column1] < 25 ) )

Filtros con operadores complejos

SELECT * FROM table1 WHERE column1 BETWEEN 1 and 5 AND column2 IN (20,30,40,50) AND column3 LIKE '%arcelona%'
df.loc[(df['colum1'].between(1,5)) & (df['column2'].isin([20,30,40,50])) & (df['column3'].str.contains('arcelona'))]
Table.SelectRows(#"Paso Anterior", each ([column1] > 1 and [column1] < 5) and List.Contains({20,30,40,50}, [column2]) and Text.Contains([column3], "arcelona") )

Join tables

SELECT t1.column1, t2.column1 FROM table1 t1 LEFT JOIN table2 t2 ON t1.column_id = t2.column_id

Hay dos funciones que pueden ayudarnos en este proceso merge y join.

df_joined = df1.merge(df2, left_on='lkey', right_on='rkey', how='left')
df_joined = df1.join(df2, on='column_id', how='left')Luego seleccionamos dos columnas
df_joined.loc[['column1_df1', 'column1_df2']]

En Power Query vamos a ir eligiendo una columna de antemano y luego añadiendo la segunda.

#"Origen" = #"Paso Anterior"[[column1_t1]]
#"Paso Join" = Table.NestedJoin(#"Origen", {"column_t1_id"}, table2, {"column_t2_id"}, "Prefijo", JoinKind.LeftOuter)
#"Expansion" = Table.ExpandTableColumn(#"Paso Join", "Prefijo", {"column1_t2"}, {"Prefijo_column1_t2"})

Group By

SELECT column1, count(*) FROM table1 GROUP BY column1
df.groupby('column1')['column1'].count()
Table.Group(#"Paso Anterior", {"column1"}, {{"Alias de count", each Table.RowCount(_), type number}})

Filtrando un agrupado

SELECT store, sum(sales) FROM table1 GROUP BY store HAVING sum(sales) > 1000
df_grouped = df.groupby('store')['sales'].sum()
df_grouped.loc[df_grouped > 1000]
#"Grouping" = Table.Group(#"Paso Anterior", {"store"}, {{"Alias de sum", each List.Sum([sales]), type number}})
#"Final" = Table.SelectRows( #"Grouping" , each [Alias de sum] > 1000 )

Ordenar descendente por columna

SELECT * FROM table1 ORDER BY column1 DESC
df.sort_values(by=['column1'], ascending=False)
Table.Sort(#"Paso Anterior",{{"column1", Order.Descending}})

Unir una tabla con otra de la misma característica

SELECT * FROM table1 UNION SELECT * FROM table2

En Pandas tenemos dos opciones conocidas, la función append y concat.

df.append(df2)
pd.concat([df1, df2])
Table.Combine({table1, table2})

Transformaciones

Las siguientes transformaciones son directamente entre Pandas y Power Query puesto que no son tan comunes en un lenguaje de consulta como SQL.

Analizar el contenido de una tabla

df.describe()
Table.Profile(#"Paso Anterior")

Chequear valores únicos de las columnas

df.value_counts()
Table.Profile(#"Paso Anterior")[[Column],[DistinctCount]]

Generar Tabla de prueba con datos cargados a mano

df = pd.DataFrame([[1,2],["Boris Yeltsin", "Mikhail Gorbachev"]], columns=["CustomerID", "Name"])
Table.FromRecords({[CustomerID = 1, Name = "Bob", Phone = "123-4567"]})

Quitar una columna

df.drop(columns=['column1'])
df.drop(['column1'], axis=1)
Table.RemoveColumns(#"Paso Anterior",{"column1"})

Aplicar transformaciones sobre una columna

df.apply(lambda x : x['column1'] + 1  , axis = 1)
Table.TransformColumns(#"Paso Anterior", {{"column1", each _ + 1, type number}})

Hemos terminado el largo camino de consultas y transformaciones que nos ayudarían a tener un mejor tiempo a puro código con Power Query y no andar buscando que botones tocar.