Probablemente el lanzamiento más importante de DAX desde sus funciones OFFSET y WINDOWS, hace tiempo Microsoft lanzó la posibilidad de crear funciones de DAX.
Como buen y robusto lenguaje de programación o de consultas a datos, era sumamente necesario que esta posibilidad exista para mejorar los desarrollos de lógicas generales o puntuales de negocio.
Si bien hoy estan preliminares o preview, escribimos este artículo para ponernos al día porque su lanzamiento podría abrir puertas a mejores desarrollos de modelado.
Para comenzar vamos introducirnos en el concepto tal como nos gusta hacer en LaDataWeb.
Microsoft dice: Las funciones definidas por el usuario (UDF) de expresiones de análisis de datos (DAX) permiten empaquetar lógica DAX reutilizable y con parámetros en los modelos, lo que facilita la escritura, el mantenimiento y el uso compartido del código DAX. En lugar de repetir fórmulas entre medidas, columnas calculadas y objetos visuales, las UDF proporcionan flexibilidad de estilo de programación a los modelos semánticos.
Esta vez creo que esta bastante clara la definición que nos deja Microsoft. Podemos pensarla como métodos de programación, procedimientos o funciones de programación.
¿Por qué las usaríamos?
- Reusar código. Tal como las variables nos ayudan a evitar computo en una medida. Las funciones podrían hacerlo en muchas medidas
- La mayoría de los lenguajes tienen funciones. No podría quedarse atrás DAX si quiere avanzar como lenguaje. Pensemos que SQL M ya lo tenían.
- Para evitar soluciones atadas con alambre. Se usaban calculation groups para adaptarlo pero no podías ponerle parámetros y no tenía la mejor performance
Antes de comenzar cabe aclarar que aún están en preview o versión preliminar. Entonces debemos activarlas desde el menú de opciones:

Las funciones pueden definir un valor de entrada que llamamos parámetro y deben establecen un valor de salida o retorno. El retorno puede ser un escalar o una tabla. La función se ejecuta en el contexto de evaluación original. ¿Qué quiere decir esto? Esto significa que son códigos que suceden dentro del lugar donde lo escribimos y no antes. Si llamamos una función en una medida, sucede en el mismo contexto que el código de la medida.
- El código puede acceder al contexto de filas o filtro
- El código no puede ser evaluado hasta que es ejecutado
- Las funciones pueden llamar otras funciones
- No permiten recursividad. No podemos llamarlas entre si de forma circular sino lineal.
Las podemos encontrar en la vista de relaciones como indica la siguiente imagen:

Veamos como se definen:
NombreFuncion = ( N o ningún parámetro ) => Resultado de la expresion
Entonces podríamos hacer algo simple como multiplicar dos nros
ValorXValor = (a , b) => a * b
Esa función simple la podríamos usar en una tradicional expresión para multiplicar fila por fila como precio * cantidad de facturas:

No olvidemos definir una descripción para facilitar su uso tanto para nosotros como para quien edite el modelo de datos.

Podemos visualizarlas de distintos lugares. Por supuesto, desde la vista de relaciones. Se suman dos lugares más. Podemos verlas desde la DAX query view:

Claro que el otro lugar es desde la poderosa TMDL View que todo lo ve, basta con arrastrar la función al canva:

En esta vista también se puede apreciar la descripción.
Demos un paso más y veamos el funcionamientos de los parámetros. Existen dos tipos de parámetros, los valores y las expresiones:
Valores
= (a : VAL ) =>
CALCULATE ( a, columna = "rojo")
VAL valor por defecto. Evaluados en el contexto donde llamamos a la función. Significa que el parámetro es calculado antes de ingresar a la función. Son como variables pre calculadas en una medida.
Expresiones
= (a : EXPR ) =>
CALCULATE ( a, columna = "rojo")
Evaluados dentro en la ejecución de la función. El parámetro que llega a la función es un código, es decir, es pasado como una expresión que se calcula en la función. Esto significa que el parámetro se calculará cada vez que la mencionamos en nuestra función.
Veamos el resultado de ambas funciones llamadas desde una medida simple que haga lo mismo para que comprendan la diferencia de ejecución de los parámetros.
Medida = Funcion ( [SalesAmount] , [SalesCost] )

Como pueden apreciar, la función de valor esta totalmente ignorando el pedido de filtrar por rojo dado que el parámetro ya tiene el valor evaluado y calculado. Por eso va restando Montos y Costos fila por fila. La función que usa la expresión como parámetro es lo contrario, intentará ejecutar el parámetro dentro del contexto del calculate y ejecutar el filtro de color = rojo. Por esta razón, en cada fila resta 116.013,39 - Costo fila por fila. Calculate pisa el contexto de filtros y asume ese valor para todos los colores.
Podemos utilizar ambos combinados en una sola función pero, a modo de buena práctica, recomiendo aclarar en el nombre del parámetro si es una expresión y sino asumir valor:
= (aExpr : EXPR, b : VAL ) =>
Retornos
Lo que la función devuelve también puede variar. Los ejemplos que vimos hasta ahora fueron para devolver un escalar, es decir, un número resultado. Sin embargo, una de las devoluciones más poderosas que podemos obtener es una tabla. Podríamos utilizarlas como filtros. Veamos un ejemplo. Tenemos una función que nos calcula las ventas para el TOP 10 de clientes que más compran.

Ahora bien. Puede que en el negocio se utilice mucho visualizar sobre la audiencia del top 10 de clientes que más compran pero de distintos resultados. Tal vez buscan también cuantos productos se llevan esos 10 clientes, cuantos gastos nos generan, cuanto margen dan, cuanto… Distintos cálculos pero siempre sobre los 10 clientes con mayores ventas. Esa tabla resultante que se filtra podría ser una función:

Fuimos un poco más allá pusimos un parámetro para definir sobre que universo de clientes queremos trabajar. Con esta función podríamos ejecutar cualquier medida contra el top X de clientes con más ventas y quedaría muy simple:

Esto nos abre una puerta a automatización de código que antes no teníamos. Ahora podemos replantearnos si el código que escribimos para reutilizar es una función exclusiva para este modelo o si es independiente de un modelo, es decir que podría funcionar en cualquier modelo. Por ejemplo, TopNCustomers, es una función exclusiva para ese modelo porque necesito una tabla Customer y medida SalesAmount para que funcione. A diferencia de ValorXValor, que podría funcionar en cualquier modelo.
En esta búsqueda reutilizar, la comunidad se está adelantando de la mano de SQLBI. Crearon un sitio donde podamos compartir bloques de código a modo de librería que pueden encontrar aquí:
Otro ejemplo simple que les comparto que utilizo mucho es una tradicional medida de semáforo para formato condicional. Si ustedes ya tienen los colores preferidos para formatos condicionales, entonces tomen este ejemplo. Veamos la función:

La función nos permite enviar por parámetro una expresión a resolverse y dos valores. Los valores se usarán para delimitar los colores del semáforo. Entonces pintar nuestra tabla nunca fue más simple, por ejemplo pintemos el margen de rentabilidad por 30% y 50%.
Color margin = Semaforo([Margin], 0.30, 0.50)
Miren que simple queda y podríamos ejecutar cualquier medida que tengamos valores de semáforo para pintar y sus delimitadores.
De forma más compleja para pintar la rentabilidad promedio de un producto según sus precentiles 25 y 75. Los percentiles agrandan la fórmula pero no perdamos el foco que con una función tenemos la medida y los límites super simple.

Luego ponemos el formato a la tabla y podríamos verla:

Esta función podría reutilizarse entre varios archivos de Power Bi. No necesariamente funciona con este modelo únicamente, sino que es un estándar. De eso se trata la página de SQLBI, de funciones que podamos reutilizar.
Como siempre decimos en LaDataWeb, la creatividad es su límite, pueden llevarlo al límite con sus requerimientos e ideas. Por supuesto, aquí esta el archivo de PowerBi en mi GitHub. Esperamos haberlos ayudado a desprender ideas.