Monday, June 9, 2008

Cálculo da variância em Python

Este post segue uma seqüência iniciada no blog Kodumaro, comparando a implementação de alguns cálculos de estatística usando linguagens como Lisp, C e depois seguido de Ruby no blog Programando sem cafeína, e Smalltalk.

Como estou vendo um pouco de estatística na faculdade agora, me animei para tentar uma implementação em Python também.

import math

def arithmetic_mean(dataset):
"""
Simple arithmetic mean (average)

Example:
>>> arithmetic_mean([10,100,1000])
370
"""
return sum(dataset) / len(dataset)


def root_mean_square(dataset):
"""
RMS (quadratic mean)

Example:
>>> root_mean_square([10,100,1000])
580.25856305616037
"""
return math.sqrt(sum(x**2 for x in dataset) / len(dataset))


def var_num(dataset, mean_function=arithmetic_mean):
"""
Divisor for variance calculation
"""
mean = mean_function(dataset)
return sum((x - mean)**2 for x in dataset)


def populational_variance(dataset):
"""
Population variance

Example:
>>> populational_variance([10,100,1000])
199800
"""
return var_num(dataset) / len(dataset)


def sample_variance(dataset):
"""
Sample variance

Example:
>>> sample_variance([10,100,1000])
299700
"""
return var_num(dataset) / (len(dataset) - 1)

Preferi implementar como funções simples pois assim é fácil de aplicar sobre qualquer conjunto de dados iterável como listas, tuplas, dicionários ou sets.

Na função de numerador da variância, um dos parâmetros é a função de média que se deseja utilizar para o cálculo, com o padrão sendo a média aritmética (pois como nota o blog Kodumaro, o uso da função depende de quem pediu o cálculo ;).

Vale notar também o uso de generator expressions para os somatórios, que substituem as mais tradicionais list comprehensions nesse caso por questão de performance, principalmente para grandes conjuntos de dados.

Apliquei ainda doctests simples para validar os resultados das funções.

Labels:

7 Comments:

Blogger Tiago Albineli Motta said...

Ficou muito boa essa versão em Python

June 10, 2008 2:00 AM  
Blogger La Batalema Pitonisto said...

Muito legal! Também gostei. =)

Só não entendi por que você usou math.sqrt().

Geralmente faço isso com pow() ou com **.

Qual o motivo de sua escolha? Há alguma diferença de precisão ou desempenho?

[]'s
Cacilhas, La Batalema

June 10, 2008 6:32 AM  
Blogger LKRaider said...

Não entendi a dúvida. Como você calcula a raiz usando potência?

Prefiro usar métodos prontos do que reimplementar na maioria dos casos. A biblioteca Python é extensa e "não tenho medo de usar" :D

June 10, 2008 6:46 PM  
Blogger João Felipe Santos said...

A operação de "raiz" é uma potenciação, só que com um valor fracionário.

math.pow(10, 0.5) == math.sqrt(10)

Mas é claro que pra raízes quadradas, math.sqrt é muito mais intuitivo ;)

Parabéns pelo post, foi bem interessante!

June 13, 2008 11:08 AM  
Blogger LKRaider said...

Tem razão ! *slaps own forehead* :)

Rodei alguns testes aqui, e a versão utilizando multiplicação é mais rápida, como pode ser visto ao rodar este código:


from timeit import Timer

print 'multiply:'
print min(Timer('10**0.5').repeat())

print 'math.sqrt:'
print min(Timer('sqrt(10)', 'from math import sqrt').repeat())


Aqui o resultado é:


multiply:
0.0717430114746
math.sqrt:
0.43367600441


Portanto, para quem quiser otimizar um pouco o código, pode trocar pela multiplicação mesmo.

O custo maior do math.sqrt ocorre por se tratar de uma chamada de função, que envolve busca no namespace:

"Function call overhead in Python is relatively high, especially compared with the execution speed of a builtin function." [1]

e nisso a operação de multiplicação direta leva vantagem.

June 13, 2008 10:41 PM  
Blogger Walter Cruz said...

Bacana ;)

July 2, 2008 7:26 AM  
Blogger LKRaider said...

Hm, troque onde eu falei "multiplicação" no comentário anterior por "potenciação", claro... :/

July 17, 2008 10:10 PM  

Post a Comment

<< Home