Dimensión horaria en M

En casi cualquier modelo de datos que diseñemos será imprescindible disponer de una dimensión temporal que nos permita filtrar y segmentar los datos de las tablas de hechos en función de los atributos temporales que nos interesen en cada momento. La dimensión temporal más común y útil corresponde a aquella de nivel de granularidad diario, donde tendremos un registro por cada día del periodo abarcado por dicha dimensión.

Por otra parte, atributos relacionados con la dimensión horaria utilizados para describir los eventos registrados en las tablas de hechos aparecen con mucha menor frecuencia. No obstante, en algunas ocasiones en las que el tiempo queda registrado con un nivel de detalle inferior al día, la posibilidad de segmentar los datos por dichos atributos se convierte en uno de los temas principales a la hora de diseñar un almacén de datos analítico.

Este nivel de granularidad temporal, aunque más frecuente, no será exclusivo de modelos científicos que necesiten una especial precisión temporal en el registro de los eventos que pretenden analizar, sino que podemos encontrar fácilmente casos, dentro del mundo empresarial, en los que la capacidad de filtrar los datos por atributos temporales de detalle inferior a un día nos proporcionará un valor añadido importante. Un ejemplo sencillo puede ser un restaurante, donde conocer las horas de mayor y menor afluencia de clientes será clave en la toma de decisiones relacionadas con el manejo de los horarios del personal.

En estos escenarios, donde tanto la fecha como la hora constituyen dimensiones relevantes, se hace necesario entender como modelar esta situación de una forma que nos permita manejar eficientemente ambas dimensiones por separado y, a la vez, poder combinarlas cuando sea conveniente.

En primer lugar, no debemos incluir información horaria dentro de la dimensión de fechas, ya que si queremos aprovechar las funcionalidades relativas al manejo del tiempo que nos ofrece Power BI, como el uso de las funciones de inteligencia de tiempo, debemos seguir las reglas del lenguaje DAX. Entre éstas se encuentra la obligatoriedad de la dimensión temporal de disponer de granularidad diaria, es decir, que cada registro de la misma corresponda a un día en el calendario.

Queremos conservar la potente utilidad de la dimensión temporal de fecha, y simultáneamente, proporcionar la capacidad de análisis hasta el nivel de minutos o segundos cuando así lo consideremos apropiado. La solución más sencilla y eficiente para conseguir dicho objetivo se basa en crear una dimensión de fecha y, separadamente, una dimensión horaria.

De una manera muy similar a como creamos una dimensión temporal de fecha en un artículo anterior, vamos a crear ahora una dimensión horaria utilizando también el lenguaje M.

Base de la consulta

Como primer paso, debemos definir la granularidad con la que queremos dotar a la dimensión. Salvo que sea estrictamente necesario, es aconsejable no incluir los segundos, ya que normalmente, en un proyecto de inteligencia de negocios de autoservicio no nos proporcionarán información adicional relevante y porque el número de registros aumenta enormemente, afectando al rendimiento del modelo. Por dichos motivos, vamos a escoger al minuto como la unidad más pequeña de nuestra dimensión.

Como existe un número fijo de minutos en un día, que corresponde a 24h * 60min/h = 1.440 minutos, no tenemos necesidad de crear parámetros que varíen el resultado de la consulta cuando ejecutamos la función, y por lo tanto nos bastará con definirla:

let CrearDimensionHoraria = () as table =>

 

Ahora solo tenemos que ir generando las variables que formarán parte de la consulta. Vamos a crear la variable que guarda el número de minutos en un día y por consiguiente, el número de registros de la tabla y, utilizando la función List.Times() , generaremos una lista continua de 1440 minutos, con un incremento de 1 minuto para cada registro:

let
  ContadorMinutos = 1440,
  Origen = List.Times(#time(0, 0, 0),ContadorMinutos, #duration(0,0,1,0)),

 

Si ejecutamos la consulta, ya tendríamos la lista creada:

Image 5

Columnas adicionales

Para crear el conjunto de atributos descriptivos que nos permitirán filtrar y segmentar nuestros datos, transformamos la lista en una tabla con la función Table.FromList(), cambiamos el tipo de dato a ‘Hora’ y cambiamos el nombre de la columna:

TablaDesdeLista = Table.FromList(Origen, Splitter.SplitByNothing()), 
TipoCambiado = Table.TransformColumnTypes(TablaDesdeLista,{{"Column1", type time}}),
NombreCambiado = Table.RenameColumns(TipoCambiado,{{"Column1", "Tiempo"}})

 

Ahora definimos columnas para cada una de las unidades de tiempo a representar en la dimensión (hora y minuto):

HoraInsertada = Table.AddColumn(NombreCambiado, "Hora", each Time.StartOfHour([Tiempo])),
MinutoInsertado = Table.AddColumn(HoraInsertada, "Minuto", each Time.Minute([Tiempo])),
TipoHoraCambiado = Table.TransformColumnTypes(MinutoInsertado,{{"Hora", type time}}),

 

Solo nos queda añadir los niveles de la jerarquía temporal que nos sean útiles para la realización de informes. Si creamos una columna que clasifique los horarios por «periodos de tiempo»,  deberemos definir también otra que nos permita ordenar dichos periodos. Es posible que, en función del formato que tenga la columna con la información horaria en la tabla de hechos, necesitemos crear también una clave horaria que yo he llamado ‘TiempoKey’, que nos servirá para relacionar las 2 tablas:

CuartoHoraInsertado = Table.AddColumn(TipoHoraCambiado, "Cuarto Hora", each if [Minuto]<15 then [Hora] else if [Minuto] < 30 then Value.Add([Hora],#duration(0,0,15, 0)) else if [Minuto] = 0 and [Hora Numero] = 3 and [Hora Numero] = 10 and [Hora Numero] = 12 and [Hora Numero] = 16 and [Hora Numero] = 0 and [Hora Numero] = 3 and [Hora Numero] = 10 and [Hora Numero] = 12 and [Hora Numero] = 16 and [Hora Numero] < 20 then 4 else 5),
TiempoKey = Table.AddColumn(InsertPeriodSort, "TiempoKey", each Time.ToText([Tiempo], "HHmm"), type text)
in
TiempoKey

 

Consideraciones finales

De la forma en la que hemos creado dos dimensiones temporales separadas (una de fecha y una de hora) podremos segmentar nuestros datos por los atributos de cualquiera de las dos, funcionando las mismas como cualquier otra tabla de búsqueda en un modelo dimensional:

Image 12

 

Asimismo, podemos combinar ambas dimensiones de forma sencilla generando una jerarquía de ‘Fecha/Hora’, colocando los atributos del elemento ‘Tiempo’ debajo de los del elemento ‘Fecha’ en una visualización, de forma que el usuario pueda explorar los datos a lo largo de toda la jerarquía, desde año > trimestre > mes > día > hora > minuto, sin importar que los atributos pertenezcan a dimensiones separadas:

Image 7

 

También podemos añadir columnas calculadas con DAX para crear otros atributos descriptivos que puedan ser útiles a los usuarios a la hora de explorar los datos. Por ejemplo, podemos generar una columna que permita segmentar o filtrar los datos por cada hora del día expresando el intervalo de forma clara:

RangoHorario =
FORMAT ( [Hora]; «Short Time» ) & » – «
FORMAT ( [Proxima Hora]; «Short Time» )

Image 2

 

Ya tenemos todo listo para crear visualizaciones con una precisión temporal de hasta un minuto:

Image 4

Código completo
let CrearDimensionHoraria = () as table =>
let
ContadorMinutos = 1440,
Origen = List.Times(#time(0, 0, 0),ContadorMinutos, #duration(0,0,1,0)),
TablaDesdeLista = Table.FromList(Origen, Splitter.SplitByNothing()), 
TipoCambiado = Table.TransformColumnTypes(TablaDesdeLista,{{"Column1", type time}}),
NombreCambiado = Table.RenameColumns(TipoCambiado,{{"Column1", "Tiempo"}}),
HoraInsertada = Table.AddColumn(NombreCambiado, "Hora", each Time.StartOfHour([Tiempo])),
MinutoInsertado = Table.AddColumn(HoraInsertada, "Minuto", each Time.Minute([Tiempo])),
TipoHoraCambiado = Table.TransformColumnTypes(MinutoInsertado,{{"Hora", type time}}),
CuartoHoraInsertado = Table.AddColumn(TipoHoraCambiado, "Cuarto Hora", each if [Minuto]<15 then [Hora] else if [Minuto] < 30 then Value.Add([Hora],#duration(0,0,15, 0)) else if [Minuto] = 0 and [Hora Numero] = 3 and [Hora Numero] = 10 and [Hora Numero] = 12 and [Hora Numero] = 16 and [Hora Numero] = 0 and [Hora Numero] = 3 and [Hora Numero] = 10 and [Hora Numero] = 12 and [Hora Numero] = 16 and [Hora Numero] < 20 then 4 else 5),
TiempoKey = Table.AddColumn(InsertPeriodSort, "TiempoKey", each Time.ToText([Tiempo], "HHmm"), type text)
in
TiempoKey
in
CrearDimensionHoraria

Todo el código DAX de este artículo ha sido formateado con «DAX Formatter»
DAX Formatter by SQLBI

 

Deja un comentario