En el Publicación anterior, discutimos cómo usar Notebooks con PySpark para la ingeniería de funciones. Si bien Spark ofrece mucha flexibilidad y potencia, puede ser bastante complejo y requiere mucho código para comenzar. No todo el mundo se siente cómodo escribiendo código o tiene tiempo para aprender un nuevo lenguaje de programación, que es donde entra en juego Dataflow Gen2.
Dataflow Gen2 es un motor de integración y transformación de datos de bajo código que le permite crear canalizaciones de datos para cargar datos de una amplia variedad de fuentes en Microsoft Fabric. Está basado en Power Query, que está integrado en muchos productos de Microsoft, como Excel, Power BI y Azure Data Factory. Dataflow Gen2 es una gran herramienta para crear canalizaciones de datos sin código a través de una interfaz visual, lo que facilita la creación de canalizaciones de datos rápidamente. Si ya está familiarizado con Power Query o no tiene miedo de escribir código, también puede utilizar el lenguaje M (“Mashup”) subyacente para crear transformaciones más complejas.
En esta publicación, explicaremos cómo usar Dataflow Gen2 para crear las mismas funciones necesarias para entrenar nuestro modelo de aprendizaje automático. Usaremos el mismo conjunto de datos que en la publicación anterior, que contiene datos sobre juegos de baloncesto universitario.
Hay dos conjuntos de datos que usaremos para crear nuestras funciones: los juegos de la temporada regular y los juegos del torneo. Estos dos conjuntos de datos también se dividen en torneos masculinos y femeninos, que deberán combinarse en un único conjunto de datos. En total hay cuatro archivos csv que deben combinarse y transformarse en dos tablas separadas en Lakehouse.
Al utilizar Dataflows, hay varias formas de resolver este problema y en esta publicación quiero mostrar tres enfoques diferentes: un enfoque sin código, un enfoque con poco código y, finalmente, un enfoque más avanzado con todo el código.
El primer enfoque, y el más sencillo, es utilizar la interfaz visual de Dataflow Gen2 para cargar los datos y crear las funciones.
Los datos
Los datos que estamos analizando son de los torneos de baloncesto universitario de EE. UU. de 2024, que se obtuvieron de la competencia Kaggle Machine Learning Mania 2024 de marzo en curso, cuyos detalles se pueden encontrar. aquíy tiene licencia CC BY 4.0
Cargando los datos
El primer paso es obtener los datos de Lakehouse, lo que se puede hacer seleccionando el botón “Obtener datos” en la cinta Inicio y luego seleccionando Más… de la lista de fuentes de datos.
De la lista, seleccione Centro de datos de OneLake para encontrar Lakehouse y luego, una vez seleccionado, busque el archivo csv en la carpeta Archivos.
Esto creará una nueva consulta con cuatro pasos, los cuales son:
- Fuente: una función que consulta Lakehouse para conocer todos los contenidos.
- Navegación 1: convierte el contenido de Lakehouse en una tabla.
- Navegación 2: filtra la tabla para recuperar el archivo csv seleccionado por nombre.
- CSV importado: convierte el archivo binario en una tabla.
Ahora que los datos están cargados, podemos comenzar con una preparación básica de los datos para ponerlos en un formato que podamos usar para crear nuestras funciones. Lo primero que debemos hacer es configurar los nombres de las columnas para que se basen en la primera fila del conjunto de datos. Esto se puede hacer seleccionando la opción “Usar la primera fila como encabezados” en el grupo Transformar en la cinta Inicio o en el elemento del menú Transformar.
El siguiente paso es cambiar el nombre de la columna “WLoc” a “ubicación” seleccionando la columna en la vista de tabla o haciendo clic derecho en la columna y seleccionando “Cambiar nombre”.
La columna de ubicación contiene la ubicación del juego, que es “H” para local, “A” para visitante o “N” para neutral. Para nuestros propósitos, queremos convertir esto a un valor numérico, donde “H” es 1, “A” es -1 y “N” es 0, ya que esto facilitará su uso en nuestro modelo. Esto se puede hacer seleccionando la columna y luego usando el Reemplazar valores… transformar en el elemento del menú Transformar.
Esto también deberá hacerse para los otros dos valores de ubicación.
Finalmente, necesitamos cambiar el tipo de datos de la columna de ubicación para que sea un número entero en lugar de texto. Esto se puede hacer seleccionando la columna y luego seleccionando el tipo de datos de la lista desplegable en el grupo Transformar en la cinta Inicio.
En lugar de repetir el paso de cambio de nombre para cada uno de los tipos de ubicación, se puede usar un poco de código M para reemplazar los valores en la columna de ubicación. Esto se puede hacer seleccionando la transformación anterior en la consulta (columnas renombradas) y luego seleccionando el botón Insertar paso en la barra de fórmulas. Esto agregará un nuevo paso y podrá ingresar el siguiente código para reemplazar los valores en la columna de ubicación.
Table.ReplaceValue(#"Renamed columns", each [location], each if Text.Contains([location], "H") then "1" else if Text.Contains([location], "A") then "-1" else "0", Replacer.ReplaceText, {"location"})
Agregar funciones
Tenemos los datos cargados, pero aún no son correctos para nuestro modelo. Cada fila del conjunto de datos representa un juego entre dos equipos e incluye los puntajes y estadísticas tanto del equipo ganador como del perdedor en una única tabla amplia. Necesitamos crear características que representen el desempeño de cada equipo en el juego y tener una fila por equipo por juego.
Para ello necesitamos dividir los datos en dos tablas, una para el equipo ganador y otra para el equipo perdedor. La forma más sencilla de hacer esto es crear una nueva consulta para cada equipo y luego fusionarlos nuevamente al final. Hay algunas formas de hacer esto; sin embargo, para mantener las cosas simples y comprensibles (especialmente si alguna vez necesitamos volver a esto más adelante), crearemos dos referencias a la consulta fuente y luego las agregaremos nuevamente, después de hacer algunas transformaciones ligeras.
Se puede hacer referencia a una columna desde el panel Consultas a la izquierda o seleccionando el menú contextual de la consulta si se usa la vista Diagrama. Esto creará una nueva consulta que hace referencia a la consulta original y cualquier cambio realizado en la consulta original se reflejará en la nueva consulta. Hice esto dos veces, una para el equipo ganador y otra para el equipo perdedor y luego cambié el nombre de las columnas anteponiéndolas con el prefijo “T1_” y “T2_” respectivamente.
Una vez establecidos los valores de las columnas, podemos volver a combinar las dos consultas usando Agregar consultas y luego crear nuestra primera característica, que es la diferencia de puntos entre los dos equipos. Esto se puede hacer seleccionando las columnas T1_Score y T2_Score y luego seleccionando “Restar” del grupo “Estándar” en la cinta Agregar columna.
Una vez hecho esto, podemos cargar los datos en Lakehouse como una nueva tabla. El resultado final debería verse así:
Existen algunas limitaciones con el enfoque sin código, la principal es que no es fácil reutilizar consultas o transformaciones. En el ejemplo anterior necesitaríamos repetir los mismos pasos otras tres veces para cargar cada uno de los archivos csv individuales. Aquí es donde copiar y pegar resulta útil, pero no es lo ideal. Veamos a continuación un enfoque de código bajo.
En el enfoque de código bajo usaremos una combinación de la interfaz visual y el lenguaje M para cargar y transformar los datos. Este enfoque es más flexible que el enfoque sin código, pero aun así no requiere escribir mucho código.
Cargando los datos
El objetivo del enfoque de código bajo es reducir la cantidad de consultas repetidas necesarias y facilitar la reutilización de las transformaciones. Para ello aprovecharemos que Power Query es un lenguaje funcional y que podemos crear funciones para encapsular las transformaciones que queramos aplicar a los datos. Cuando cargamos por primera vez los datos de Lakehouse, se crearon cuatro pasos; el segundo paso fue convertir el contenido de Lakehouse en una tabla, y cada fila contenía una referencia a un archivo csv binario. Podemos usar esto como entrada a una función, que cargará el csv en una nueva tabla, usando la transformación de función personalizada Invocar para cada fila de la tabla.
Para crear la función, seleccione “Consulta en blanco” en el menú Obtener datos, o haga clic derecho en el panel Consultas y seleccione “Nueva consulta” > “Consulta en blanco”. En la nueva ventana de consulta, ingrese el siguiente código:
(TableContents as binary) =>let
Source = Csv.Document(TableContents, [Delimiter = ",", Columns = 34, QuoteStyle = QuoteStyle.None]),
PromoteHeaders = Table.PromoteHeaders(Source, [PromoteAllScalars = true])
in
PromoteHeaders
El código de esta función se ha copiado de nuestro enfoque inicial sin código, pero en lugar de cargar el archivo csv directamente, requiere un parámetro llamado Contenido de la tablalo lee como un archivo csv Csv.Document y luego establece la primera fila de los datos para que sean los encabezados de las columnas Table.PromoteHeaders.
Luego podemos usar la transformación de función personalizada Invocar para aplicar esta función a cada fila de la consulta de Lakehouse. Esto se puede hacer seleccionando la transformación “Invocar función personalizada” en la cinta Agregar columna y luego seleccionando la función que acabamos de crear.
Esto creará una nueva columna en la consulta de Lakehouse, con todo el contenido del archivo csv cargado en una tabla, que se representa como [Table] en la vista de tabla. Luego podemos usar la función expandir en el encabezado de la columna para expandir la tabla en columnas individuales.
El resultado combina efectivamente los dos archivos csv en una sola tabla, a partir de la cual podemos continuar creando nuestras funciones como antes.
Todavía existen algunas limitaciones con este enfoque, aunque hemos reducido la cantidad de consultas repetidas, todavía necesitamos duplicar todo para los conjuntos de datos de la temporada regular y de los juegos del torneo. Aquí es donde entra en juego el enfoque de todo código.
El enfoque de todo código es el más flexible y potente, pero también requiere que se escriba la mayor cantidad de código. Este enfoque es más adecuado para quienes se sienten cómodos escribiendo código y desean tener control total sobre las transformaciones que se aplican a los datos.
Básicamente, lo que haremos será tomar todo el código M que se generó en cada una de las consultas y combinarlos en una sola consulta. Esto nos permitirá cargar todos los archivos csv en una única consulta y luego aplicar las transformaciones a cada uno de ellos en un solo paso. Para obtener todo el código M, podemos seleccionar cada consulta y luego hacer clic en el Editor avanzado en la cinta Inicio, que muestra todo el código M que se generó para esa consulta. Luego podemos copiar y pegar este código en una nueva consulta y luego combinarlos todos.
Para hacer esto, necesitamos crear una nueva consulta en blanco y luego ingresar el siguiente código:
(TourneyType as text) => let
Source = Lakehouse.Contents(null){[workspaceId = "..."]}[Data]{[lakehouseId = "..."]}[Data],
#"Navigation 1" = Source{[Id = "Files", ItemKind = "Folder"]}[Data],
#"Filtered rows" = Table.SelectRows(#"Navigation 1", each Text.Contains([Name], TourneyType)),
#"Invoked custom function" = Table.AddColumn(#"Filtered rows", "Invoked custom function", each LoadCSV([Content])),
#"Removed columns" = Table.RemoveColumns(#"Invoked custom function", {"Content", "Name", "Extension", "Date accessed", "Date modified", "Date created", "Attributes", "Folder Path", "ItemKind", "IsLeaf"}),
#"Expanded Invoked custom function" = Table.ExpandTableColumn(#"Removed columns", "Invoked custom function", {"Season", "DayNum", "WTeamID", "WScore", "LTeamID", "LScore", "WLoc", "NumOT", "WFGM", "WFGA", "WFGM3", "WFGA3", "WFTM", "WFTA", "WOR", "WDR", "WAst", "WTO", "WStl", "WBlk", "WPF", "LFGM", "LFGA", "LFGM3", "LFGA3", "LFTM", "LFTA", "LOR", "LDR", "LAst", "LTO", "LStl", "LBlk", "LPF"}, {"Season", "DayNum", "WTeamID", "WScore", "LTeamID", "LScore", "WLoc", "NumOT", "WFGM", "WFGA", "WFGM3", "WFGA3", "WFTM", "WFTA", "WOR", "WDR", "WAst", "WTO", "WStl", "WBlk", "WPF", "LFGM", "LFGA", "LFGM3", "LFGA3", "LFTM", "LFTA", "LOR", "LDR", "LAst", "LTO", "LStl", "LBlk", "LPF"}),
#"Renamed columns" = Table.RenameColumns(#"Expanded Invoked custom function", {{"WLoc", "location"}}),
Custom = Table.ReplaceValue(#"Renamed columns", each [location], each if Text.Contains([location], "H") then "1" else if Text.Contains([location], "A") then "-1" else "0", Replacer.ReplaceText, {"location"}),
#"Change Types" = Table.TransformColumnTypes(Custom, {{"Season", Int64.Type}, {"DayNum", Int64.Type}, {"WTeamID", Int64.Type}, {"WScore", Int64.Type}, {"LTeamID", Int64.Type}, {"LScore", Int64.Type}, {"location", Int64.Type}, {"NumOT", Int64.Type}, {"WFGM", Int64.Type}, {"WFGA", Int64.Type}, {"WFGM3", Int64.Type}, {"WFGA3", Int64.Type}, {"WFTM", Int64.Type}, {"WFTA", Int64.Type}, {"WOR", Int64.Type}, {"WDR", Int64.Type}, {"WAst", Int64.Type}, {"WTO", Int64.Type}, {"WStl", Int64.Type}, {"WBlk", Int64.Type}, {"WPF", Int64.Type}, {"LFGM", Int64.Type}, {"LFGA", Int64.Type}, {"LFGM3", Int64.Type}, {"LFGA3", Int64.Type}, {"LFTM", Int64.Type}, {"LFTA", Int64.Type}, {"LOR", Int64.Type}, {"LDR", Int64.Type}, {"LAst", Int64.Type}, {"LTO", Int64.Type}, {"LStl", Int64.Type}, {"LBlk", Int64.Type}, {"LPF", Int64.Type}}),
Winners = Table.TransformColumnNames(#"Change Types", each if Text.StartsWith(_, "W") then Text.Replace(_, "W", "T1_") else Text.Replace(_, "L", "T2_")),
#"Rename L" = Table.TransformColumnNames(#"Change Types", each if Text.StartsWith(_, "W") then Text.Replace(_, "W", "T2_") else Text.Replace(_, "L", "T1_")),
#"Replaced Value L" = Table.ReplaceValue(#"Rename L", each [location], each if [location] = 1 then -1 else if Text.Contains([location], -1) then 1 else [location], Replacer.ReplaceValue, {"location"}),
Losers = Table.TransformColumnTypes(#"Replaced Value L", {{"location", Int64.Type}}),
Combined = Table.Combine({Winners, Losers}),
PointDiff = Table.AddColumn(Combined, "PointDiff", each [T1_Score] - [T2_Score], Int64.Type)
in
PointDiff
Nota: los valores de conexión de Lakehouse se han eliminado
Lo que está pasando aquí es que estamos:
- Cargando los datos de Lakehouse;
- Filtrar las filas para incluir solo los archivos csv que coincidan con el parámetro TourneyType;
- Cargando los archivos csv en tablas;
- Expandir las tablas en columnas;
- Cambiar el nombre de las columnas;
- Cambiar los tipos de datos;
- Combinando las dos mesas nuevamente;
- Calculando la diferencia de puntos entre los dos equipos.
Usar la consulta es tan simple como seleccionarla y luego invocar la función con el parámetro TourneyType.
Esto creará una nueva consulta con la función como fuente y los datos cargados y transformados. Entonces es solo cuestión de cargar los datos en Lakehouse como una nueva tabla.
Como puede ver, la función LoadTournamentData se invoca con el parámetro “RegularSeasonDetailedResults” que cargará los juegos de la temporada regular masculina y femenina en una sola tabla.
¡Y eso es!
Esperamos que esta publicación le haya brindado una buena descripción general de cómo usar Dataflow Gen2 para preparar datos y crear funciones para su modelo de aprendizaje automático. Su enfoque de código reducido facilita la creación rápida de canalizaciones de datos y contiene muchas funciones potentes que se pueden utilizar para crear transformaciones complejas. Es una excelente primera opción para cualquiera que necesite transformar datos, pero lo que es más importante, tiene la ventaja de no necesitar escribir código complejo que sea propenso a errores, difícil de probar y de mantener.
Al momento de escribir este artículo, los flujos de datos Gen2 no son compatibles con la integración de Git, por lo que no es posible controlar las versiones ni compartir los flujos de datos. Se espera que esta característica sea lanzado en el cuarto trimestre de 2024.