Funciones Recursivas en Power BI / Power Query

  • Facebook
  • Twitter
  • LinkedIn

¿Alguna vez has oído hablar de recursividad o funciones recursivas? Están presentes en el lenguaje M para Power BI / Power Query y esta es una publicación en la que repasaré cómo usar la recursividad o hacer funciones recursivas en Power BI / Power Query.

Este es un tema bastante avanzado sobre Power BI / Power Query y el lenguaje M.

He intentado hacer todo lo posible por hacer una lectura simple, pero te recomiendo que leas en mis publicaciones anteriores sobre «Parámetros y Funciones» (1 | 2 | 3 | 4), «Lógica Condicional» (1 | 2 | 3 ) y Navegación en columnas antes de leer este artículo.

¿Qué es la recursión?

En resumen, la recursión es un método para resolver un problema en el que la solución depende de soluciones o cálculos para casos más pequeños del mismo problema.

La forma en que esto sucede es que dentro de Power BI / Power Query, una función puede llamarse a sí misma dentro de su propio código usando el signo @.

Esto es increíblemente poderoso desde una perspectiva de rendimiento en contraste con algo así como iteraciones (como la función List.Generate).

Veamos primero un ejemplo técnico y luego veremos un ejemplo más práctico de recursión en Power BI / Power Query.

Ejemplo técnico

En Power Query, o en el lenguaje M, hay una función llamada Number.Factorial que hace un factorial como se muestra en esta página wiki.

Imaginemos este escenario por un segundo:

  1. Actualmente estás trabajando en Microsoft junto a Curt, Matt, Miguel Ll., Ehren y el resto de la gente que trabaja en Power Query.
  2. La función Number.Factorial no existe todavía en el lenguaje M
  3. Se te ha encomendado la misión de crear una nueva función factorial M a partir de las funciones M existentes

¿Cómo harías que eso suceda?

Tiene 2 formas de hacer que eso suceda y la más fácil sería usar una función con el nombre de List.Accumulate:

Ahora, esto funciona y dará los resultados correctos, pero se parece más a una envoltura, porque realmente no te dice lo que está pasando detrás de la escena.

Solo la gente de Microsoft sabe exactamente qué sucede detrás de la escena de la función List.Accumulate, por lo que queremos ir un paso más allá y adoptar un enfoque más explícito.

Para eso, crearemos nuestra propia función recursiva que tiene el siguiente script:

El primer paso, llamado Source, de esa función simplemente crea una lista continua de números desde 1 hasta la entrada (y).

El segundo paso, denominado Count, solo nos da un total (Count) de los elementos en esa lista.

Quiero que prestes mucha atención a lo que sucede desde las líneas 5 a 10, donde defino el tercer paso (en realidad una función) con el nombre de fxFactorial.

Esta función toma 3 argumentos:

  • Una lista de números (x)
  • Un número que actúa como contador (n)
  • Un número inicial para el factorial (initial)

La lógica es la siguiente: pasamos la lista que creamos en el paso Source al primer argumento de esta función, el segundo argumento será el número 0 (el contador comenzará desde 0) y el último argumento será nulo (o null).

Por eso ves que el último paso de toda la función es:

Custom1 = fxFactorial(Source,0, null)

Lo que sucederá dentro de la función es que tratará de averiguar si el argumento inicial se establece en nulo, si es así, se multiplicará el primer elemento de la lista por 1.

En nuestro caso, eso será multiplicar el número 1 por 1.

El segundo paso de esa función simplemente verifica qué elementos de la lista se usaron para este cálculo y para verificar si debemos continuar con el siguiente elemento (si hay alguno) o no. Esa es la línea # 8 en la que utilizo la instrucción if y la @ – la @ es la parte crucial porque ahí es donde llamo recursivamente esa función basada en esa instrucción if, pero note que esta vez los argumentos de esa función son diferentes.

Sigo pasando la misma lista del primer argumento, pero el segundo argumento es «n + 1» y recuerda que n fue el número 0 inicialmente, por lo que ahora será 1. Esa n es esencialmente un contador y el último argumento es el resultado del Cálculo anterior, por lo tanto, llamé a ese paso como Cálculo y el parámetro se llama «initial».

Una vez más, la parte crucial de todo esto es hacer un cálculo por elemento y tener en cuenta el resultado de un cálculo anterior para un nuevo cálculo.

Es por eso que utilizamos el signo @ (at) en Power Query: se usa para la recursión y es un método de programación dentro del lenguaje M que permite que una función se llame a sí misma cuando sea necesario.

Al poner esto en práctica, verás que cuando invocamos esta función, el resultado es correcto. En mi caso, he probado esto con el 5!:

Ejemplo practico

El ejemplo anterior es algo puramente técnico que tal vez nunca veas en el mundo real, pero te da un poco de teoría y antecedentes sobre por qué, desde una perspectiva de programación, se necesita recursión y cómo ya se implementa en algunas funciones M para ti.

La recursión no suele ser algo que yo hago. De hecho, en mis 5 años usando esta herramienta, solo he necesitado la recursión 3 veces y estoy a punto de mostrarte uno de esos escenarios que me mostró mi buen amigo Bill Szysz (YouTube).

Hace unos años publiqué este video:

Acerca del escenario: cadenas de texto de reemplazo masivo

En Power BI / Power Query, siempre que desees reemplazar una cadena de texto dentro de una columna, debes hacerlo de uno en uno. Imagina que tienes 10 reemplazos que debes hacer, que se traducirán en 10 veces que tendrás que hacer clic en el botón » Replace values » y pasar por el menú de configuración:

La idea es hacer que esto sea completamente automático para que pueda hacerse en solo 1 paso y ahorrar tiempo.

Para que eso suceda, tenemos 2 tablas:

  • Nuestra tabla fuente – que tiene una columna que contiene cadenas de texto que queremos reemplazar

  • Una tabla de traducción –que contiene pares de valores. Uno para el [OldText] y otro para el [NewText] que debe reemplazar el texto antiguo.

En mi intento original, utilicé un proceso de iteración con List.Generate, pero no fue óptimo al tratar con vastas cantidades de filas, por lo que Bill encontró un mejor enfoque con una función recursiva.

Veamos este caso más profundo. Puede seguir adelante descargando el archivo completo desde el siguiente botón:

Descargar Archivo

Paso 1: Cargar las tablas

Lo primero que debemos hacer es cargar ambas tablas de este libro de trabajo en Power Query solo como conexión. Puedes usar el archivo para conectarse directamente a él como una tabla / rango de excel o conectarse a través de un libro en blanco o un archivo de escritorio de Power BI.

El objetivo es tener las dos tablas mencionadas anteriormente cargadas.

Paso 2: Buffer las columnas OldText y NewText

Vamos a crear una nueva consulta y en ella almacenaremos las columnas de nuestra TranslationTable.

La razón principal por la que hacemos esto es por motivos de rendimiento. Buffering se asegurará de que tengamos esas columnas disponibles a un ritmo muy rápido.

Para eso, nuestra consulta comenzará así:

let
Origen = Tareas,
Old = List.Buffer(TranslationTable[OldText]),
New = List.Buffer(TranslationTable[NewText])

in

New

En esta consulta, solo estamos cargando la Tabla que tiene la columna «Tarea», y luego hemos almacenado las columnas de la tabla de traducción por separado.

Paso 3: Crea la función recursiva

Ahora necesitamos unirlo todo y crear nuestra función recursiva. Nuestro método será crear un nuevo paso con el nombre de fxTranslate y el código para ese paso será el siguiente:

fxTranslate = (x as table, n as number, ColName as text ) as table =>

       let

           Replace = Table.ReplaceValue(x, Old{n}, New{n}, Replacer.ReplaceText,{ColName}),
Checking = if n = List.Count(Old)-1 then Replace else @fxTranslate(Replace, n+1, ColName )
in
Checking

Veamos este código más profundo.

Los parámetros de esta función son:

  • x – esta es la tabla de entrada para la función
  • n – recuerda que esto es similar al ejemplo anterior. Esto es sólo un contador.
  • ColName – aquí es donde ingresamos el nombre de la columna dentro de la tabla x en la que queremos reemplazar las cadenas de texto

Ahora veamos los dos pasos en esa función:

  • Replace – aquí es donde usamos la función Table.ReplaceValue. Pasaremos la tabla y luego usaremos referencias a los elementos de las columnas de la tabla de traducción. ¿Recuerdas que agregamos 2 pasos (OLD y NEW) y almacenamos los resultados dentro de esos 2? aquí es donde los usamos y, finalmente, también pasamos el nombre de la columna de la tabla x que debe usarse para el reemplazo..
  • Checking – aquí es donde usamos una lógica condicional para saber si queremos seguir usando la recursión o no. Todo está basado en el contador. Recuerda que el contador comenzará desde 0, y 0 recuperará el primer elemento de los pasos OLD y NEW. Comparamos ese contador con el total de elementos dentro del Paso old (que es una lista) y mientras no hayamos revisado todos los elementos de esa lista, seguiremos haciendo la recursión y es por eso que tenemos el @fxTranslate (Replace, n + 1, ColName). Aquí es donde se define la recursión.

Hasta ahora, nuestro código debería verse así:

let
Origen = Tareas,
Old = List.Buffer(TranslationTable[OldText]),
New = List.Buffer(TranslationTable[NewText]),
fxTranslate = (x as table, n as number, ColName as text ) as table =>

                let

                    Replace = Table.ReplaceValue(x, Old{n}, New{n}, Replacer.ReplaceText,{ColName}),
Checking = if n = List.Count(Old)-1 then Replace else @fxTranslate(Replace, n+1, ColName )
in
Checking

in

fxTranslate

Paso 4: Invoque la función recursiva contra la tabla

Ahora que tenemos la función y todas las piezas en nuestra consulta, podemos invocar la función como el último paso para que nuestro código termine pareciéndose a esto:

Conclusión

Ahora probablemente te estarás preguntando, ¿cuándo deberías usar funciones recursivas? ¡Y esa es una pregunta difícil! Realmente depende de su escenario y si necesita trabajar, ten una función que te funcione recursivamente o no. La mayoría de los usuarios avanzados intentan ir hacia List.Acumulate o un enfoque List.Generate, pero a veces el único enfoque óptimo es crear una función recursiva y realmente depende de su escenario.

3 Comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.