jueves, 30 de noviembre de 2017

Descubriendo tendencias en un rango

A raíz de una consulta de un lector, respecto a la forma de descubrir tendencias dentro de un rango propongo el siguiente post.
[...]Tengo un conjunto de 20 datos ubicados en la columna F, desde F1 a F20. Que función podría usar para identificar si 7 datos consecutivos de esos datos muestran un comportamiento ascendente o si 7 datos consecutivos de esos datos muestran un comportamiento descendente[...]

Veamos el planteamiento.
A partir del listado de valores en el rango descrito F1:F20 queremos descubrir si existe una secuencia de siete valores consecutivos crecientes:



Una primera solución sería aplicando una fórmula en un rango adyacente, tal como se ve en la imagen anterior, donde en G2 insertamos la fórmula:
=SI((F2>=F1)*1=0;0;G1+(F2>=F1)*1)
que luego arrastraremos hasta G20.
Este cálculo nos permite ver si existe o no una secuencia creciente...

A partir de este rango podemos construir una fórmula condicional sencilla que nos indique cuál ha sido la máxima tendencia creciente. En H1 insertamos:
=SI(MAX(G2:G20)>=7;"Crecimiento de "&MAX(G2:G20);"")

De esta forma visualizamos dónde se encuentra esa tendencia y hasta donde crece.


Otra posibilidad la encontramos en la programación de VBA para Excel donde podemos crear una UDF en un módulo estándar de nuestro proyecto VBA:

Function FxTendencias(rngDatos As Range, tope As Long) As String
'rngDatos será el rango a analizar
'tope el valor que nos indique la longitud de la tendencia

'iniciamos contadores
x = 0: y = 0: num = 1: contador = 0
'recorremos el rango de celdas
For dato = 2 To rngDatos.Count
num = num + 1
    'identificamos crecimiento entre celdas
    If rngDatos.Item(dato).Value >= rngDatos.Item(dato - 1).Value Then
        x = x + 1: y = Application.Max(x, y)
        contador = contador + 1
        'registramos máximos cuando superemos el tope elegido
        If y >= tope Then
            UD = rngDatos.Item(dato).Value
            fila = rngDatos.Item(num).Row
            cuenta = Application.Max(contador, cuenta)
        End If
    Else
        x = 0: y = 0: contador = 0
    End If
Next dato

'devolvemos el dato a la hoja
FxTendencias = "Tendencia de " & cuenta & " en el rango: F" & (fila - 7) & ":F" & fila
End Function


Al ejecutarla en una celda de la hoja de cálculo:

Descubriendo tendencias en un rango


En este caso, la función personalizada, nos permite también identificar el rango donde se produce la tendencia...

martes, 28 de noviembre de 2017

Valor máximo de las sumas de un rango

Un lector planteaba una cuestión un tanto curiosa. Deseaba conocer cuál era el valor máximo de entre las sumas de un rango de Excel.
El punto de partida es una tabla de puntuaciones sobre los gustos de tres coches distintos:

Valor máximo de las sumas de un rango



La pregunta concreta a resolver es '¿Qué coche gustó más ?'.
Se puede entender desde dos perspectivas.
¿Cuál recibió una mayor puntuación directa entre los votantes?.
La respuesta en este caso es algo más sencilla. Basta construir la siguiente matricial (recuerda ejecutarla presionando Ctrl+Mayusc+Enter):
=INDICE($C$2:$E$2;1;MIN(SI($C$3:$E$5=MAX(C3:E5);COLUMNA($A:$C);MAX(C3:E5)+1)))

Valor máximo de las sumas de un rango



Esta fórmula matricial evalúa celda por celda del rango con las puntuaciones (C3:E5) verificando si es la máxima puntuación, en cuyo caso devuelve l aposición de su columna dentro del rango (columna 1, 2 o 3).
Finalmente, basándonos en esa columna, recuperamos la marca más votada desde el encabezado de los coches (C2:E2).


Una segunda forma de entender la pregunta planteada (algo más compleja) es la que me interesa.
¿Cuál es la más votada entre las sumas de las votaciones?.
La fórmula matricial que responde esta cuestión es:
=INDICE(C2:E2;1;COINCIDIR(MAX(INDICE(C3:E3+C4:E4+C5:E5;0));C3:E3+C4:E4+C5:E5;0))

Valor máximo de las sumas de un rango



Por partes esta fórmula mostraría:
MAX(INDICE(C3:E3+C4:E4+C5:E5;0))
el valor máximo de entre las sumas.
También podríamos haber expuesto:
MAX(C3:E3+C4:E4+C5:E5)
(en ambos casos ejecutadas matricialmente!!).


En nuestro ejemplo concreto:
Ferrari 12
McLaren 15
Porsche 14
las fórmulas anteriores devuelven 15...


Posteriormente, localizamos la posición de ese 15 entre el resto de sumas con la función COINCIDIR:
COINCIDIR(MAX(INDICE(C3:E3+C4:E4+C5:E5;0));C3:E3+C4:E4+C5:E5;0)
que será un 2.

Este 2 dentro de la función INDICE final recupera el nombre del coche con una suma mayor de puntuaciones.

jueves, 23 de noviembre de 2017

Opción Usar nombre de tablas en fórmulas

Hoy toca un recordatorio necesario de recordar cuando trabajamos con tablas.
La opción de Usar nombre de tablas en fórmulas.
Esta opción nos permite utilizar los nombres de los campos o elementos dentro de una tabla cuando construimos nuestras fórmulas y/o funciones.
Dentro de las muchas opciones existentes en Excel esta quizá es una de las más importantes por la facilidad que nos proporciona a la hora de construir nuestros informes.


¿Qué hace esta opción cuando está activada?. Sencillo permite referirnos a las partes de una tabla como si de un 'nombre definido' se tratara.
Véase en la imagen siguiente:

Opción Usar nombre de tablas en fórmulas



Se observa que en lugar de usar las referencias de estilo A1 habituales, los rangos o celdas sobre laa que trabajamos, emplean los nombres correspondientes de esa tabla.

Para activar o desactivar esta opción accederemos a la ficha Archivo > Opciones de Excel > Fórmulas > sección TRabajo con fórmulas > opción Usar nombre de tablas en fórmulas

Opción Usar nombre de tablas en fórmulas



Si la opción estuviera deshabilitada veríamos nuestras fórmulas y/o funciones con referencias estilo A1 'normales':

Opción Usar nombre de tablas en fórmulas



Las referencias ya existentes permanecerían con el estilo con el que fueron creadas...

Como curiosidad final si abrimos u libro de Excel 97-2003 (.xls) en modo de compatibilidad desde nuestro Excel versión 'actual', y creamos una Tabla, al trabajar sobre ésta, veremos nuestras referencias en estilo de celda A1,
quedando desactivada la opción comentada.

martes, 21 de noviembre de 2017

VBA: .DeleteAfterSubmit Eliminar email recién enviado

En una formación reciente un alumno planteaba la duda de cómo conseguir eliminar automáticamente desde Excel el último correo electrónico enviado...
La idea era automatizar envíos masivos de emails desde Excel e ir eliminando cada correo remitido.


Para dar respuesta al alumno emplearemos la propiedad .DeleteAfterSubmit asociado al objeto MailItem.
En el blog hemos escrito algunos artículos sobre la forma de enviar emails desde Excel empleado la aplicación de Outlook (ver post).. asi que nos centraremos en esta propiedad comentada.


Nuestra macro añadida dentro de un módulo estándar de nuestro proyecto de VB:

Sub EnvioMail_y_Borrado()
Dim olApp As Object
Dim olMailItm As Object

'Creamos 'la aplicación de MS Outlook'
'que será la que voy a utilizar
Set olApp = CreateObject("Outlook.Application")

'Emplearemos el email creado en la línea anterior
'y genero un email vacío... de momento
Set olMailItm = olApp.CreateItem(0)
With olMailItm
    'indicamos quién es el destinatario... y lo incluimos en el apartado de 'Para...'
    'podíamos haber empleado las opciones .CC (con copia)
    'o también .BCC (con copia oculta)
    .to = "cursos@excelforo.com"
    '.CC = "xx@excelforo.com"
    '.BCC = "yy@excelforo.com"
    'cuál es el asunto...
    .Subject = "Excelforo: prueba para eliminar email enviado"
    ' y cual el cuerpo del correo electrónico
    .Body = "Esto es una prueba de envío de emails desde Excel" & vbCrLf & _
            "El objetivo es eliminarlo automáticamente una vez enviado..." & vbCrLf & _
            "Saludos cordiales!"
    'si quisieramos ver el email...
    '.Display
    'indicamos que se elimnaré después del envío
    .DeleteAfterSubmit = True
    'y lo enviamos...
    .Send
End With

'Dejamos limpias las variables empleadas
Set olMailItm = Nothing
Set olApp = Nothing
End Sub



Tras ejecutar la macro, el resultado es el esperado...El email enviado se ha eliminado de la carpeta de Enviados.

jueves, 16 de noviembre de 2017

Gráfico Dinámico de una Pirámide Poblacional

Hoy trabajaremos sobre una base de datos de un listado de personal separados por sexo y edad...
A partir del cuál obtendremos un gráfico dinámico que represente la distribución poblacional por rango de edades.

Nuestros datos de partida son la tabla de empleados y un rango donde indicamos cuáles son los rangos de edad:

Gráfico Dinámico de una Pirámide Poblacional



Puesto que para solucionar este asunto necesitaremos crear un elemento calculado no será posible emplear un campo agrupado (el de Edad), por tanto el primer paso consistirá en traslados con una función BUSCARV el rango de edad correspondiente según la edad particular de cada empleado.
Añadimos un campo nuevo a la tabla donde incorporamos la función:
=BUSCARV([@Edad];$F$3:$H$7;3;1)

Gráfico Dinámico de una Pirámide Poblacional


Notemos que nuestro BUSCARV emplea una búsqueda aproximada, para lo cual el rango F3:H7 debe estar ordenado en sentido ascendente en su primera columna!!.

El siguiente paso consiste en generar la tabla dinámica.
Llevaremos el campo 'Rango' al área de filas, el campo 'Sexo' al área de columnas y el campo 'Empleados' al área de valores (resumido por cuenta).

Gráfico Dinámico de una Pirámide Poblacional



A continuación crearemos un elemento calculado dentro del campo 'Sexo', donde lo formularemos para obtener el dato del conteo obtenido por el elemento M en negativo:

Gráfico Dinámico de una Pirámide Poblacional


Este elemento calculado se ha podido incorporar por no existir operaciones no permitidas (promedios, desviaciones o varianzas) ni campos agrupados...

Sobre el campo 'Sexo', ya con el nuevo elemento, aplicamos un filtro para mostrar los elementos 'M-'
y 'H'.

El siguiente paso consiste en insertar un gráfico dinámico de tipo barras 'agrupadas' al que aplicaremos los pasos descritos en este post.
Básicamente consiste en Superponer las series y Disminuir el ancho del intervalo.



Logrando el objetivo buscado...

martes, 14 de noviembre de 2017

Formato Condicional Ajustado a columnas

Veremos hoy cómo aplicar un sencillo formato condicional para mostrar celdas formateadas de acuerdo al valor de una tercera celda.
Este post nace por la necesidad expresada por una lectora:
[...]existe la posibilidad de pintar celdas en Excel con un condicional que diga coloree las siguientes x numero de celdas, por ejemplo:
Numero de piezas: 10
Entonces necesito que cambie el color de diez celdas para que sobre ellas se puedan anotar los nombres de cada pieza.[...]

Formato Condicional Ajustado a columnas



Veamos la resolución del caso concreto para valores de 1 hasta 10.
1- Seleccionamos el rango B2:K11. Me aseguro que mi celda activa es B2.
2- Accedo al formato condicional > Nueva Regla > Utilice una fórmula que determine las celdas para aplicar formato.
3- Introducimos la siguiente fórmula:
=COLUMNA()<=$A2+1

Formato Condicional Ajustado a columnas



Listo. Con esa simple fórmula solucionamos el caso propuesto por nuestra amiga.
La fórmula aplicada únicamente compara la columna de cada celda con el valor de la celda (columna A)-ajustada con un +1 para concretar correctamente columna vs valor.


Este formato condicional expuesto funciona correctamente, sin duda... pero tiene una deficiencia difícil de combatir. Y es que manualmente tendríamos que ajustar/modificar nuestra regla de formato condicional, en cuanto a la propiedad 'Se aplica a' para el caso en que el intervalo de los datos (columna A) no estuviera claramente delimitado entre 0 y 10.
Dicho de otro modo, cómo ajustar el rango donde aplicar nuestra regla independientemente de los datos a transformar (sean 10 o 200).

Tenemos, pues, que conseguir que se autoajuste el rango donde aplicar nuestra regla.
Por desgracia el campo donde incluimos la propiedad 'Se aplica a' no admite fórmulas, ni nombres definidos... solo y exclusivamente rangos (como objeto), lo que hace imposible con el estándar obtener el dinamismo perseguido.
Por tanto lograremos nuestra meta con una macro.


Nuestra macro siguiente recorrerá las posibles reglas de formato condicional existentes en nuestra hoja de trabajo, buscando aquella cuya fórmula aplicada coincida con la descrita anteriormente (=COLUMNA()<=$A2+1), en cuyo caso aplicando el método .ModifyAppliesToRange sobre dicho formato condicional.
Este método modificará el rango donde aplicar la regla, tal como queríamos; quedando ajustada al rango exacto según el valor máximo.

Para facilitar el trabajo calculamos en la celda A1 el dato máximo ( =MAX(A2:A11) ), que emplearemos en nuestra macro.


Añadimos el siguiente código en un módulo estándar de nuestro proyecto de VB... aunque también podríamos asociar la macro a un evento de hoja tipo _Change:

Sub CambioFC()
Dim FC As FormatCondition
Dim sh As Worksheet

Set sh = Hoja2
For Each FC In sh.Cells.FormatConditions
    '.AppliesTo.Address, .Type, .Formula1, .Interior.Color, .Font.Name
    'buscamos la regla de formato condicional con nuestra fórmula
    If FC.Formula1 = "=COLUMNA()<=$A2+1" Then
        'y modificamos el rango donde aplicar la regla de formato condicional
        'ajustando la celda final del rango con el valor máximo de los datos
        FC.ModifyAppliesToRange Range(Cells(2, "B"), Cells(11, Range("A1").Value + 1))
    End If
Next FC
End Sub

Al ejecutar la macro conseguimos nuestro objetico de modificar y ajustar el rango donde aplicar nuestra regla de formato condicional.

jueves, 9 de noviembre de 2017

Formas de extraer Fecha y Hora de una fecha completa

Daremos respuesta a una consulta planteada por una lectora, donde planteaba la manera de recuperar a partir de una fecha completa (fecha + hora) de manera separada la fecha (día/mes/año) y por otra parte la hora (hora:minuto:segundo).

Partiremos de una fecha completa y veremos cómo empleando algunas funciones específicas de tiempo y otras matemáticas podemos obtener lo deseado.

extraer Fecha y Hora de una fecha completa



Para recuperar la fecha (día/mes/año) podemos actuar de diferentes modos:
1-Empleando la función FECHA que permite construir una fecha a partir de tres elementos... año, mes y día que obtenemos con las funciones AÑO, MES y DIA:
=FECHA(AÑO(B2);MES(B2);DIA(B2))
aplicamos un formato de Fecha Corta y listo.

2-Sabiendo que las fechas son ordinales (números enteros) que comienza el 1/1/1900, basta aplicar la función ENTERO sobre el número para descartar la parte decimal (la de las horas..).
=ENTERO(B2)

3-y una tercera forma con la función FECHANUMERO aplicada sobre la función TEXTO que convierte en texto con formato ("dd/mm/aaaa") la fecha completa:
=FECHANUMERO(TEXTO(B2;"dd/mm/aaaa"))


De forma similar trabajamos para recuperar la hora (hora:minuto:segundo).
1-Usando la función NSHORA y las funciones HORA, MINUTO Y SEGUNDO para informar de los tres argumentos que componen una hora cualquiera:
=NSHORA(HORA(B6);MINUTO(B6);SEGUNDO(B6))
aplicamos un formato de hora (por ejemplo hh:mm:ss).

2-con la función HORANUMERO sobre la función TEXTO para tratar la fecha con formato "hh:mm:ss".. de manera similar a la fecha:
=HORANUMERO(TEXTO(B6;"hh:mm:ss"))

3-recuperando la parte decimal, i.e., la de las horas quitando al número completo su parte entera:
=B6-ENTERO(B6)

4-y por último un método curioso. Un efecto matemático, al dividir un número decimal entre uno, el resto será siempre la parte decimal... por tanto:
=RESIDUO(B6;1)
nos retorna únicamente la parte decimal del 'número' que corresponde a la hora (hora:minuto:segundo).. tal como necesitamos.


Y estas son las formas más habituales de conseguir Fecha y Hora por separado...

martes, 7 de noviembre de 2017

VBA: Una macro para imprimir las hojas impares

En el post de hoy hablaremos de cómo conseguir una matriz dinámica empleando la sentencia ReDim Preserve.

Todo nace por la cuestión planteada por un lector para imprimir las hojas impares de nuestro libro de trabajo.


Lo interesante del código en cuestión es que nos permite ir redimensionando el tamaño de nuestra matriz de trabajo según vamos recorriendo las diferentes hojas del libro (cuando cumplan la condición dada), esto lo conseguimos con la sentencio REDIM; y por otro lado la instrucción PRESERVE permite almacenar o retener los elementos guardados con anterioridad!!.

La macro que veremos a continuación realiza un proceso mediante el cual carga los nombres de las hojsa impares en una matriz-array dinámica, para terminar seleccionando los elementos de dicha array y lanzarlos a la vista preliminar.

Nuestra macro añadida dentro de un módulo estándar de nuestro proyecto de VB:

Sub imprimir_hojas()
Dim hoja As Integer
Dim sel() As String 'definimos nuestra Array

x = 1
For hoja = 1 To Sheets.Count
    'condición que limita a las hojas impares
    If (hoja Mod 2) <> 0 Then
        'cambiamos la dimensión de la array
        'manteniendo los datos cargados
        ReDim Preserve sel(1 To x) As String
        'añadimos nuevo elemento a la matriz
        sel(x) = Sheets(hoja).Name
        'y terminamos aumentando el número de elementos...
        x = x + 1
    End If
Next hoja

'mandamos las hojas impares a vista preliminar
Sheets(sel).PrintPreview
End Sub



Tras ejecutar la macro, el resultado es el esperado....