La ley de Benford

La ley de Benford es una de esas regularidades con las que la naturaleza nos muestra su lado más matemático. Esta ley afirma que, para determinados conjuntos de números, la frecuencia esperada de los que comienzan por x es igual a E(fx) = log10(x + 1) – log10(x) = log10(1 + 1/x), en donde x puede tomar cualquier valor entero.

La propia ley, además, cuando dejamos que x tome valores que tienen exactamente la misma longitud o precisión, nos proporciona una suerte de función de densidad de la probabilidad de que un número tomado al azar de un conjunto de números comience por x. Por ejemplo, si dejamos que x tome valores entre 1 y 9 (evidentemente, no hay números que comiencen por cero), obtendremos las siguientes frecuencias esperadas para cada x (notemos que la suma de estas frecuencias es precisamente 100%):

x E(fx)
1 30,1030%
2 17,6091%
3 12,4939%
4 9,6910%
5 7,9181%
6 6,6947%
7 5,7992%
8 5,1153%
9 4,5757%
100,0000%

Cuando, como en este caso, dejamos que x tome valores de longitud 1, la ley de Benford también se conoce como ley del primer dígito. Pero, en general, x puede tomar valores de cualquier precisión (y estaremos entonces ante la ley de los dos primeros dígitos, de los tres primeros dígitos…); la única restricción es que, a la hora de plantear el análisis sobre un conjunto de números, todos los valores de x deben tener exactamente la misma precisión (un dígito, dos dígitos…), para que la suma de las frecuencias sea del 100%.

No sólo eso. También podemos trabajar sobre la expresión de la ley de Benford y deducir que también hay una ley del segundo dígito. En efecto, como podemos conocer directamente (ley de Benford de los dos primeros dígitos) la frecuencia esperada de los números de un conjunto de datos que comienzan por 11, 21, 31, 41, 51, 61, 71, 81 y 91, la frecuencia esperada de números cuyo segundo dígito sea 1 será la suma de estas frecuencias (todos los números de un conjunto cuyo segundo dígito es 1 tienen que comenzar por 11, 21, 31, etc.). Añadir que, en este caso, el cero también tiene asignada una frecuencia esperada puesto que puede perfectamente aparecer como segundo dígito (y su frecuencia esperada será la suma de las frecuencias esperadas de los números que comienzan por 10, 20, 30, 40, 50, 60, 70, 80 y 90).

Como siempre, todo esto debería quedar más claro con un sencillo ejemplo en T-SQL. Supongamos que somos capaces de obtener un conjunto de importes a analizar en busca de posibles desviaciones de la ley de Benford; supondremos que dicho conjunto es un cursor denominado #origen y que el campo donde se guardan los valores a verificar se denomina Importe y es un decimal de precisión 12 con dos decimales. La ley de Benford es invariante ante cambios de escala así que lo primero que tenemos que hacer es multiplicar por 100 nuestros importes para trabajar con números enteros (en realidad, esto lo hacemos para evitar que pueda haber importes que empiecen por cero; es cierto que, en estos casos, podríamos buscar la primera cifra diferente de cero pero creo que esta forma de proceder es más sencilla). No hemos hecho ningún supuesto acerca de si el campo importe puede o no ser negativo; para mayor generalidad trabajaremos con el valor absoluto. Por la misma razón, filtraremos aquellos importes que pudieran ser nulos o cero.

Lo primero que debemos hacer es contar cuántos movimientos van a entrar en nuestro cálculo. Para ello, declaramos una variable entera y le asignamos el resultado de dicha cuenta:

declare @n as integer
select @n = count(*)
from #origen
where isnull(Importe, 0) <> 0

A continuación ya podemos efectuar el análisis de frecuencias de nuestro conjunto de datos con esta expresión de consulta (expresión en la que el cast a decimal en el denominador del campo FrecuenciaObservada lo usamos para que el servidor devuelva el resultado del cociente como decimal):

select
left(100*abs(Importe), 1) as PrimerDigito,
log10(1 + left(100*abs(Importe), 1)) -
log10(left(100*abs(Importe), 1)) as FrecuenciaEsperada,
count(*) as NumeroSucesos,
cast(count(*) as decimal(12, 2))/@n as FrecuenciaObservada
from #origen
where isnull(Importe, 0) <> 0
group by
left(100*abs(Importe), 1),
log10(1 + left(100*abs(Importe), 1))
order by 1

Yo he utilizado 3.234.510 importes procedentes de uno de esos conjuntos de números a los que les es aplicable la ley de Benford (en concreto, números procedentes de la contabilidad de una empresa) y mis resultados para el análisis del primer dígito han sido los siguientes:

x E(fx) Frecuencia absoluta Frecuencia observada
1 30,1030% 972.746 30,0740%
2 17,6091% 563.870 17,4329%
3 12,4939% 393.331 12,1605%
4 9,6910% 305.350 9,4404%
5 7,9181% 278.202 8,6011%
6 6,6947% 227.149 7,0227%
7 5,7992% 185.023 5,7203%
8 5,1153% 165.301 5,1105%
9 4,5757% 143.538 4,4377%
100,0000% 3.234.510

Para los dos primeros dígitos he elaborado el siguiente gráfico que compara los valores observados (en barras) con los esperados (representados por la línea azul):

Análisis 2 Dígitos

Nos quedan un par de puntos para terminar este post y son los siguientes. En primer lugar, tenemos que insistir que la ley de Benford se cumple sólo para determinados conjuntos de números y no para cualquier conjunto de números. Básicamente, no pueden ser números generados aleatoriamente ni tampoco números que tenga un significado (códigos postales, por ejemplo); tampoco pueden ser números que tengan un máximo y/o un mínimo determinados y, como siempre, conviene que la muestra tenga un tamaño lo suficientemente grande.

En segundo lugar, aclarar que la más importante aplicación de la ley de Benford al campo de las Finanzas está en la Auditoría Forense (en un curso de Auditoría Forense fue precisamente donde oí hablar por primera vez de ella) para la detección de fraudes. En concreto, vemos que en el gráfico de nuestro ejemplo hay algunos “picos”, excesos de apariciones en nuestro conjunto de datos sobre las apariciones estimadas, de números que empiezan por 20, 50 y 69. Tendríamos que bajar al detalle para ver de dónde provienen esas cifras y podríamos ver que los excesos de números que empiezan por 69 corresponden en muchos casos a pagos de entre 690 y 699 euros efectuados contra una única cuenta por un empleado del Departamento de Tesorería con capacidad para emitir transferencias de hasta… ¿lo adivinan? Efectivamente: 700 euros. Pero, cuidado, los “picos” en el 20 y en torno al 50, aunque son mayores que el del 69, se corresponden con operaciones normales de la empresa. En realidad, hay que combinar técnicas estadísticas de contraste de hipótesis con técnicas de auditoría de cuentas para poder llegar a estar seguros, muy seguros, con una certeza casi absoluta, de que estamos ante un fraude.  Cometer aquí un error puede arruinar nuestra imagen de auditor forense…


Percentiles y conjuntos de datos

A veces es necesario organizar un determinado conjunto de datos en percentiles (o en n-tiles). Por ejemplo, podría ser necesario organizar la información de ventas a clientes de forma que pudiésemos hacer una clasificación ABC de los mismos. En este post vamos a suponer que hemos decidido que el trabajo de proceso de la información va a tener lugar en un servidor SQL Server de Microsoft (durante mucho tiempo el que escribe ha sido un firme partidario de que la lógica de las aplicaciones estuviese únicamente en el software y de que las bases de datos fuesen poco más que meros repositorios de información, pero los tiempos cambian y hoy en día responsabilizar de ciertos procesos al servidor de base de datos proporciona múltiples ventajas que no podemos obviar).

Si efectuamos una aproximación superficial a los percentiles en SQL Server podríamos pensar que una llamada a la función NTILE() de Transact-SQL pasándole un 100 como parámetro resolvería nuestro problema. Pero desgraciadamente la función NTILE() organiza la información según los registros y no según los valores de un campo numérico de esos registros (v. esta entrada en la MSDN) y, por lo tanto, no sirve para nuestro propósito. En el ejemplo que estamos manejando, si tenemos 320 clientes, el primer percentil que devuelva la función NTILE() estaría ocupado por los 3 ó 4 primeros clientes (realmente por los primeros 3,20 clientes), independientemente de la cifra de ventas que éstos tuvieran.

Vamos, pues, a tratar de desarrollar un procedimiento que nos devuelva un conjunto de datos con información acerca del percentil ocupado por cada registro. Supongamos que partimos de una tabla (o de una vista, o del resultado de una consulta, de un procedimiento almacenado o de una función) denominada VENTAS que tiene únicamente dos campos: un identificador de cliente (IdCliente) y las ventas acumuladas a ese cliente para el período de que se trate (ImporteVentas).

Lo primero que tenemos que hacer es ordenar dicha tabla de menor a mayor importe acumulado de las ventas, guardando la posición de cada registro (después la vamos a necesitar). Además, vamos a trabajar con una tabla temporal para la que ya en este primer paso crearemos todos los campos necesarios:

/* Borramos la tabla temporal si existe */
if object_id('tempdb..#tmp') is not null
drop table #tmp

select row_number() over (order by q.ImporteVentas) as Fila,
q.IdCliente,
q.ImporteVentas,
cast(0 as money) as SaldoAcumulado,
cast(0 as smallmoney) as Percentil,
' ' as ABC
into #tmp
from Ventas q
order by row_number() over (order by q.ImporteVentas)

A continuación debemos acumular el saldo, lo cual puede ser hecho con la siguiente actualización de la tabla temporal:

update #tmp set SaldoAcumulado =
(select sum(abs(q.ImporteVentas))
from #tmp q
where q.fila <= #tmp.fila)

Fijémonos en que estamos acumulando el valor absoluto del saldo, lo cual hace que el proceso funcione bien incluso si hay clientes con ventas netas negativas.

Para calcular el importe total de las ventas, declaramos la variable correspondiente y efectuamos el cálculo:

declare @suma money
select @suma = sum(abs(#tmp.ImporteVentas))
from #tmp

Ya podemos calcular el percentil correspondiente y asignar cada cliente al grupo (A, B o C) correspondiente en función de dichos percentiles (aquí asumo que pertenecen al grupo C los clientes situados hasta el percentil 80, éste incluido; no habría problema en suponer que el percentil 80 pertenece al grupo B; el mismo comentario puede efectuarse para los clientes situados en el percentil 95 con respecto a los grupos B y A):

update #tmp set percentil = 100 * isnull(SaldoAcumulado, 0) / @suma
update #tmp set abc =
case
when percentil <= 80 then 'C'
when percentil <= 95 then 'B'
else 'A'
end

Llegado a este punto, podemos dar por resuelto el problema ya que en la tabla #tmp tenemos el percentil que corresponde a cada cliente. Espero que pueda seros de utilidad.