2015-01-28

Ley de Benford en Ms Access

Title La ley de Benford, también conocida como la ley del primer dígito, se refiere a la frecuencia de distribución del primer dígito en muchos de los números que aparecen en la vida real. En esta distribución, el 1 aparece con una frecuencia aproximada del 30% mientras que el 9 aparece con una frecuencia menor del 5%. Por primer dígito se refiere al primer dígito no nulo o significativo.

Esta ley se puede aplicar a una gran variedad de fuentes de datos: facturas de electricidad, direcciones de calles, precios de acciones, cifras de población, tasas de mortalidad, longitud de los ríos o constantes físicas y matemáticas. Se ha aplicado en la detección de fraudes en contabilidad, resultados electorales y científicos.

Ejemplo

Queremos calcular la frecuencia relativa del campo Cargo de la tabla Pedidos de la base de datos Neptuno. El resultado final es una tabla con los dígitos, su frecuencia absoluta, el total de registros y la frecuencia porcentual.

Solución 1

En dos pasos:

  1. Consulta intermedia (qry_Total): calcula el total de registros de la tabla cuyo último dígito del campo Cargo es mayor que cero.
  2. SELECT COUNT(Pedidos.Cargo) AS Total
    FROM Pedidos
    WHERE (((Mid([Cargo],1,1))>0));
    
  3. Consulta final (qry_BenfordLaw) que calcula la frecuencia relativa usando el total de la consulta anterior.

SELECT Mid([Cargo],1,1) AS Dígito, 
COUNT(Pedidos.Cargo) AS Frecuencia, 
qry_Total.Total, 
COUNT([Pedidos]![Cargo])/[Total] AS Porcentaje
FROM Pedidos, qry_Total
GROUP BY Mid([Cargo],1,1), qry_Total.Total
HAVING (((Mid([Cargo],1,1))>0));

Solución 2

Creando una subconsulta evitando la consulta intermedia.

SELECT Mid([Cargo],1,1) AS Dígito, 
COUNT(Pedidos.Cargo) AS Frecuencia, 
(select count([Cargo]) FROM Pedidos WHERE (((Mid([Cargo],1,1))>0))) AS Total, 
Count([Pedidos]![Cargo])/[Total] AS Porcentaje
FROM Pedidos
GROUP BY Mid([Cargo],1,1)
HAVING (((Mid([Cargo],1,1))>0));
La subconsulta es idéntica a la consulta intermedia (qry_Total) que realizamos en la solución 1.

(SELECT COUNT([Cargo]) FROM Pedidos WHERE (((Mid([Cargo],1,1))>0))) AS Total

Referencias

2015-01-26

Compactar y reparar Access cada cierto tiempo

Title

Problema

Ms Access permite compactar y reparar automáticamente nuestra base de datos al cerrarla. Aunque así nos aseguramos de compactar la base de datos regularmente, esta opción presenta ciertas limitaciones. La principal es que incrementa el tiempo que tarda en cerrarse la base de datos. Además, si ésta es de gran tamaño y entramos en ella frecuentemente la acción es aún más lenta y redundante.

Solución

En lugar de compactar y reparar cada vez que salgamos de la base de datos, programamos Access para que compacte y repare nuestra base de datos cada cierto tiempo.

  1. Creamos la siguiente función en un módulo.
  2. ' Benjamín Martín-Palanco
    Public Function Compactar()
    
        Dim fs As Object, f As Object, s As String
        Dim i As Date, j As Date
        
        Set fs = CreateObject("Scripting.FileSystemObject")
        Set f = fs.GetFile(CurrentDb.Name)
         
         i = f.DateLastModified 
         j = Now - i
         
        Set fs = Nothing: Set f = Nothing
         
         If j > 8 / 24 Then  ' Cada 8 horas. 1 = 24 horas.
            Application.SetOption "Auto compact", True
            Else
            Application.SetOption "Auto compact", False
         End If
         
    End Function
    
    
  3. Creamos una macro que ejecute la función.
  4. Guardamos la macro como Autoexec para que se ejecute automáticamente al abrir la base de datos.

Notas

Creamos una función que se ejecutará automáticamente cada vez que abramos la base de datos. Ésta comprobará si hemos sobrepasado el tiempo definido. En caso afirmativo compactará al cerrar, en caso negativo nos permitirá salir sin compactar.

  • Utilizamos FileSystemObject (FSO) para acceder a la propiedad última fecha de modificación (DateLastModified) de la base de datos actual (CurrentDb.Name). Restamos la fecha actual de la fecha de última modificación y si es mayor que el tiempo especificado —ochos horas en nuestro ejemplo— activará la opción compactar al cerrar con Application.SetOption "Auto compact".
  • Al nombrar como Autoexec la macro que ejecuta la función, nos aseguramos de que al abrir la base de datos se ejecute automáticamente. En este caso no será necesario cerrar y volver a abrir la base de datos para que la opción tenga efecto, lo que sucedería si seleccionamos manualmente dicha opción en la base de datos actual. Si deseamos que al abrir la base de datos la macro no se ejecute, mantenemos presionada la tecla MAYÚS.

Referencias

2015-01-22

Medidas de tendencia central en histogramas en R: moda

Title En esta entrada añadiremos a los histogramas otra medida de tendencia central: la moda. Completamos así una entrada anterior en la que añadimos la media y la mediana.

Asimétrica negativa o a la izquierda

media < mediana < moda

    rbeta(n, 5, 2)

Simétrica

media = mediana = moda

    rbeta(n, 5, 5)

Asimétrica positiva o a la derecha

media > mediana > moda

    rbeta(n, 2, 5)
    parámetro x de legend = "topright"

Código

Instalamos y cargamos el paquete modeest, para calcular la moda de las distribuciones con las funciones betaMode y normMode.

install.packages("modeest")
library(modeest)
Empleamos la función de densidad de beta:

dbeta(x, shape1, shape2, ncp = 0, log = FALSE)

Modificaremos los parámetros de la distribución beta a (shape1) y b (shape2) para alterar la forma de la distribución y que sea asimétrica negativa, simétrica o asimétrica positiva.

Asimétrica negativa: shape1 = 5, shape2 =2
Simétrica: shape1 = 5, shape2 =5
Asimétrica positiva: shape1 = 2, shape2 =5

# Ejemplo: asimétrica negativa
set.seed(2014)
vble  <- rbeta(1000000, 5, 2) # Parametros a modificar

# Histograma
hist(vble, 
     prob = TRUE,
     xlim = c(0, 1),
     col = "slategray2",
     border = "white",
     main = "Asimetría negativa", 
     xlab = "", 
     las = 1) 

# Función de densidad
lines(density(vble), # density plot
      lwd = 2, # thickness of line
      col = "darkblue")

# Líneas
  # Media
mean  <- mean(vble)
segments(x0 = mean, y0 = 0, 
         x1 = mean, y1 = dbeta(mean, 5, 2), # Parametros a modificar
         col = "blue", lwd = 2) # lty = 3 dotted line
  # Mediana
median <-  median(vble)
segments(x0 = median, y0 = 0, 
         x1 = median, y1 = dbeta(median, 5, 2), # Parametros a modificar
         col = "red", lwd = 2)
  # Moda
mode <- betaMode(5, 2)
segments(x0 = mode, y0 = 0, x1 = mode, 
         y1 = dbeta(mode, 5, 2), 
         col = "orange", lwd = 2)

# Leyenda
legend(x = "topleft", # Ubicación de la leyenda
       c("Función de densidad", "Media", "Mediana"),
       col = c("darkblue", "blue", "red"),
       lwd = c(2, 2, 2),
       bty = "n")

Alternativa

Podemos añadir las líneas de la media y mediana mediante la función abline. La diferencia respecto al ejemplo anterior con segments es que la línea cortará a la función de densidad pues es infinita.

set.seed(2014)
vble  <- rnorm(n = 1000000) 
hist(vble, 
     prob = TRUE,
     xlim = c(-3,3), 
     ylim = c(0, .4),
     col = "slategray2",
     border = "white",
     main = "Simétrica", 
     xlab = "", 
     las = 1) 

# Función de densidad
lines(density(vble), # density plot
      lwd = 2, # thickness of line
      col = "darkblue")
# Media
abline(v = mean(vble),
       col = "blue",
       lwd = 2)
# Mediana
abline(v = median(vble),
       col = "red",
       lwd = 2)
# Mode
abline(v = normMode(),
       col = "orange",
       lwd = 2)

Entradas relacionadas

  • Calcular la moda en R usando el paquete modeest
  • Medidas de tendencia central en histogramas en R: media y mediana
  • Generar una distribución normal aleatoria en R
  • Operaciones básicas con la distribución normal en R
  • 2015-01-20

    Calcular la moda en R usando el paquete modeest

    Title R no dispone de una función en su paquete base que nos permita calcular la moda. La función mode devuelve el tipo o modo de almacenamiento de un objeto. Hay múltiples formas de calcular la moda haciendo uso de otras funciones de R. Sin embargo, ahora optamos por cargar el paquete modeest y usar la función mlv que devuelve el valor de un vector numérico.

    # Si modeest no está instalado y cargado
    install.packages("modeest") 
    library(modeest)
    
    Usamos como ejemplo el data frame trees.

    mlv(trees$Volume, method = "mfv") # O mlv(trees$Volume, method = "discrete")
    
    Mode (most frequent value): 10.3 
    Bickel's modal skewness: 0.8709677 
    Call: mlv.default(x = trees$Volume, method = "discrete") 
    
    Si tan sólo queremos el valor más frecuente:

    mlv(trees$Volume, method = "mfv")[1]
    

    Calcular la moda de múltiples columnas

    apply(trees, 2, mlv,  method = "mfv")
    
    $Girth
    Mode (most frequent value): 13.325 
    Bickel's modal skewness: -0.1612903 
    Call: mlv.default(x = newX[, i], method = "discrete") 
    
    $Height
    Mode (most frequent value): 80 
    Bickel's modal skewness: -0.3870968 
    Call: mlv.default(x = newX[, i], method = "discrete") 
    
    $Volume
    Mode (most frequent value): 10.3 
    Bickel's modal skewness: 0.8709677 
    Call: mlv.default(x = newX[, i], method = "discrete") 
    

    Entradas relacionadas

    2015-01-18

    Mensaje de error en Access: no se pudo eliminar nada en las tablas especificadas

    Title Cuando queremos realizar una consulta de eliminación que emplea varias tablas puede aparecer el siguiente mensaje de error: No se pudo eliminar nada en las tablas especificadas.

    Ejemplo

    Empleamos la base de datos Neptuno y creamos una consulta de eliminación con las tablas Detalles de pedidos y Pedidos. Queremos eliminar todos aquellos pedidos, y sus detalles, que contengan al menos un producto con descuento.

    Al ejecutar la consulta Access muestra el mensaje:

    Solución

    1. Abrimos la hoja de propiedades de la consulta.
    2. Establecemos la propiedad Registros únicos en Sí.
    3. Ejecutamos la consulta de nuevo

    Resultado

    Elimina 380 registros de la tabla Pedidos y quedan 450 pedidos con productos sin ningún descuento. Además, habremos eliminado 1.045 registros de la tabla Detalles de pedidos. Esto es debido a que en las relaciones entre ambas tablas están activadas la casillas Exigir integridad referencial y Eliminar en cascada los registros relacionados. Access elimina automáticamente todos los registros que hacen referencia a la clave principal (IdPedido) al eliminarse el registro que contiene la clave principal.

    En la tabla Detalles de pedidos quedan 1.110 productos. Cuando mostramos la consulta en modo Ver nos muestra los 838 registros que tienen algún descuento. Pero al ejecutar la consulta también borra aquellos productos de pedidos que contienen algún producto descontado (comparten el mismo IdPedido): 1.045.

    Explicación

    Instrucción SQL de la consulta antes de cambiar la propiedad de Registros únicos a No.

    DELETE Pedidos.*, [Detalles de pedidos].Descuento
    FROM Pedidos INNER JOIN [Detalles de pedidos] ON Pedidos.IdPedido = [Detalles de pedidos].IdPedido
    WHERE ((([Detalles de pedidos].Descuento)>0));
    Instrucción SQL de la consulta antes de cambiar la propiedad de Registros únicos a Sí.

    DELETE DISTINCTROW Pedidos.*, [Detalles de pedidos].Descuento
    FROM Pedidos INNER JOIN [Detalles de pedidos] ON Pedidos.IdPedido = [Detalles de pedidos].IdPedido
    WHERE ((([Detalles de pedidos].Descuento)>0));
    Al establecer la propiedad Registros únicos en Sí, Access añade DISTINCTROW al predicado de la consulta SQL. En nuestro ejemplo, la tabla Pedidos no contiene registros duplicados de IdPedido, pero la tabla Detalles de pedidos sí, pues cada pedido incluye diferentes productos. Al indicar DISTINCTROW genera una lista de pedidos única con al menos un registro en Detalles de pedidos. Si omitimos DISTINCTROW genera varias filas para cada una de los pedidos que tengan más de un registro en Detalles de pedido.

    Referencias

    2015-01-15

    Corrector ortográfico en español para R

    Title Hace algunos años Peter Norvig escribió el artículo How to Write a Spelling Corrector, creando un corrector ortográfico en 21 líneas de Python. Recientemente, encontré una versión creada por Rasmus Bååth para R, Peter Norvig's Spell Checker in Two Lines of Base R. En esta entrada me baso en el código de Rasmus Bååth para crear una versión en español. Antes de continuar, recomiendo leer los artículos anteriores.

    Código desglosado

    # Código modificado
    raw_test <- read.csv("https://sites.google.com/site/nubededatosblogspotcom/crea.txt", 
              header = FALSE) # Importa como data frame
    sorted_words  <- as.table(as.matrix(raw_test)) # Convierte data frame a table
    
    # Código idéntico al de Rasmus Bååth  
    correct <- function(word) {
      # Calcula la distancia entre la palabra y el resto de palabras ordenadas (sorted words).
      edit_dist <- adist(word, sorted_words)
      # Calcula la distancia mínima y devuelve una palabra existente en crea.txt
      # con un límite de 2 ediciones.
      min_edit_dist <- min(edit_dist, 2)
      # Genera un vector con todas las palabras con el mínimo de distancia.
      # Como sorted_words está ordenada de más a menos común, el vector
      # resultante tendrá la primera coincidencia más común/probable.
      proposals_by_prob <- c(sorted_words[ edit_dist <= min(edit_dist, 2)])
      # En caso de que proposals_by_prob esté vacío asignamos la palabra evaluada
      proposals_by_prob <- c(proposals_by_prob, word)
      # ... y devuelve la palabra primera/más probable en el vector.
      proposals_by_prob[1]
    }
    

    Funcionamiento

     correct("correrctor")
    [1] "corrector"
     correct("ortogrfaico")
    [1] "ortográfico"
     correct("wn")
    [1] "en"
     correct("foncionamento")
    [1] "funcionamiento"
    

    El código define la función correct y como argumento incluimos entre comillas la palabra evaluada. La distancia máxima entre la palabra evaluada y el objetivo (una palabra incluida en el corpus, correcta) es de dos ediciones. Pues, de acuerdo a Norvig, la literatura sobre el tema afirma que la inmensa mayoría de errores ortográficos se encuentran a una distancia de 2 o 1. Si la palabra está incluida en el corpus o si no encuentra una corrección de la misma en dos ediciones, no corregirá dicha palabra.

    # Distancia 2 ediciones
    correct("edictioness") 
    [1] "ediciones"
    # Distancia 3 ediciones
     correct("edictiomess")
    [1] "edictiomess"
    

    Notas

    Básicamente el código original, prácticamente idéntico al código con nuestro propio corpus, extraía las palabras del fichero big.txt de Norvig y creaba un vector en orden descendente en función de la frecuencia de las palabras. Inicialmente pensé que bastaría con crear fichero equivalente en español, concatenando textos en español del proyecto Gutenberg.

    Pero buscando un listado de palabras más frecuentes encontré el Corpus de Referencia del Español Actual (CREA), un listado de frecuencias de palabras. Las ventajas de utilizar el CREA frente a una recopilación de textos mía son:

    - Mayor representatividad de la frecuencia de las palabras por la selección de texto de la RAE.

    - Simplificación del código pues ya no hay que extraer o contar la frecuencia de cada palabra.

    - Fichero de texto de menor tamaño.

    Una desventaja es que si quisiéramos hacer un corrector más sofisticado, como por ejemplo tener en cuenta el contexto de la palabra dentro del texto, tal y como menciona Norvig, el Corpus de Referencia del Español Actual (CREA) no sería de ayuda.

    La lista total de frecuencias comprende 737.799 palabras, pero para ganar en rapidez he seleccionado las primeras 100.000 en el fichero crea.txt. El código anterior (código desglosado) importa este fichero de texto, ya en orden descendente, como data frame y lo transforma en la clase table de R. Después utiliza la función creada por Rasmus Bååth.

    Código con nuestro propio corpus

    En el caso de que queramos crear nuestro propio corpus agrupando texto en español.

    # En dos líneas
    sorted_words <- names(sort(table(strsplit(tolower(paste(readLines("corpus.txt"), collapse = " ")), "[^abcdefghijklmnñopqrstuvwxyzáéíóúäëïöüçàèìòùâêîôû']+")), decreasing = TRUE))
    correct <- function(word) { c(sorted_words[ adist(word, sorted_words) <= min(adist(word, sorted_words), 2)], word)[1] }
    
    # Desglosado
    raw_text <- paste(readLines("corpus.txt"), collapse = " ")
    split_text <- strsplit(raw_text, "[^abcdefghijklmnñopqrstuvwxyzáéíóúäëïöüçàèìòùâêîôû']+")
    sorted_words  <- as.table(as.matrix(data.frame(split_text))) 
    
    correct <- function(word) {
      edit_dist <- adist(word, sorted_words)
      min_edit_dist <- min(edit_dist, 2)
      proposals_by_prob <- c(sorted_words[ edit_dist <= min(edit_dist, 2)])
      proposals_by_prob <- c(proposals_by_prob, word)
      proposals_by_prob[1]
    }
    
    He sustituido el rango de caracteres "[^a-z]+" por "[^abcdefghijklmnñopqrstuvwxyzáéíóúäëïöüçàèìòùâêîôû']+" especificando los caracteres para que al crear el vector con las palabras no las divida erróneamente cuando encuentre acentos, apostrofes o letras como la ñ y la ç.

    Código en dos líneas

    # Código modificado
    sorted_words <- as.table(as.matrix(read.csv("https://sites.google.com/site/nubededatosblogspotcom/crea.txt", header = FALSE)))
    
    # Código idéntico al de Rasmus Bååth  
    correct <- function(word) { c(sorted_words[ adist(word, sorted_words) <= min(adist(word, sorted_words), 2)], word)[1] }
    
    # Código original de Rasmus Bååth
    sorted_words <- names(sort(table(strsplit(tolower(paste(readLines("http://www.norvig.com/big.txt"), collapse = " ")), "[^a-z]+")), decreasing = TRUE))
    
    correct <- function(word) { c(sorted_words[ adist(word, sorted_words) <= min(adist(word, sorted_words), 2)], word)[1] }
    

    Referencias

    2015-01-13

    Calcular días transcurridos y restantes del año

    Title Para calcular el nº de días transcurridos o restantes hasta o desde una fecha dada calculamos la diferencia entre la fecha deseada y el principio o final de año. Bien especificamos el 01/01/Año y el 31/12/Año en otra celda o bien dentro de la propia celda usando las funciones fecha y año. Si la fecha deseada es hoy sustituimos B2 por HOY() para que se actualice automáticamente. Formateamos las celdas según corresponda: como fecha y general o número.

    Días transcurridos

    C2=B3-B2+1
    C3=B3-FECHA(AÑO(B3);1;1)+1
    C4=B3-FECHA(AÑO(B3);1;0)
    

    'Respecto a hoy
    =HOY()-FECHA(AÑO(HOY());1;0) 
    ' En inglés
    =TODAY()-DATE(YEAR(TODAY());1;0)
    

    En C4 en el argumento día de la función FECHA escribimos 0 para obtener el último día del mes anterior.

    Días restantes

    G2=F3-F2
    G3=FECHA(AÑO(F2);12;31)-F2 
    

    ' Respecto a hoy
    =FECHA(AÑO(HOY());12;31)-HOY()
    'En inglés
    =DATE(YEAR(TODAY());12;31)-TODAY()
    

    Excel 2013

    En Excel 2013 podemos usar la nueva función DIAS para calcular el número de días entre dos fechas.

    =DIAS(B3;B2)+1 ' Inicio de año
    =DIAS(F3;F2)   ' Final de año
    

    Entradas relacionadas

    • Calcular días transcurridos y restantes del año en R

    2015-01-09

    Regresión lineal simple en R con ggplot2

    Title En una entrada anterior vimos un ejemplo de regresión lineal simple y creamos el diagrama de dispersión y la recta de regresión con funciones del paquete graphics. En esta ocasión emplearemos el paquete ggplot2. Como ejemplo, usamos los datos cats del peso corporal y del corazón de una muestra gatos del paquete MASS en R.

    Código

    library(MASS)
    library(ggplot2)
    ggplot(cats, aes(Bwt, Hwt))+
      geom_point(shape = 1)+   
      geom_smooth(method = lm, # Recta de regresión
                  se = FALSE)  # Oculta intervalo de confianza
    

    Diagrama de dispersión

    Notas

    Con la función qplot podemos obtener un gráfico muy similar.

    qplot(Bwt, Hwt, data = cats)+ 
      stat_smooth(method = lm, se = FALSE)
    

    Referencias

    Nube de datos