diff --git a/pymath/__init__.py b/pymath/__init__.py index f4effb6..53fb338 100644 --- a/pymath/__init__.py +++ b/pymath/__init__.py @@ -2,7 +2,7 @@ # encoding: utf-8 from .calculus import Expression, Polynom, Fraction, random_str -from .stat import Dataset +from .stat import Dataset, WeightedDataset # ----------------------------- # Reglages pour 'vim' diff --git a/pymath/stat/__init__.py b/pymath/stat/__init__.py index 9573133..f5b4d75 100644 --- a/pymath/stat/__init__.py +++ b/pymath/stat/__init__.py @@ -2,6 +2,7 @@ # encoding: utf-8 from .dataset import Dataset +from .weightedDataset import WeightedDataset # ----------------------------- # Reglages pour 'vim' diff --git a/pymath/stat/weightedDataset.py b/pymath/stat/weightedDataset.py index c2d00fe..1727891 100644 --- a/pymath/stat/weightedDataset.py +++ b/pymath/stat/weightedDataset.py @@ -7,95 +7,79 @@ # # -from math import sqrt, cos, ceil +from math import sqrt, ceil +from collections import Counter +from .dataset import Dataset +from ..calculus.generic import flatten_list -class Serie(): - """ Classe réprésentant un série statistique avec rendu latex """ + +class WeightedDataset(dict): + """ A weighted dataset with statistics and latex rendering methods - def __init__(self, valeurs = None, val_name = "Valeurs", effectifs = None, eff_name = "Effectifs", random = 0): + >>> w = WeightedDataset([1, 2, 3, 4], "Enfants", [10, 11, 12, 13]) + >>> print(w) + {1: 10, 2: 11, 3: 12, 4: 13} + >>> w.effectif_total() + 46 + >>> w.sum() + 120 + >>> w.mean() + 2.608695652173913 + >>> w.deviation() + 56.95652173913044 + >>> w.variance() + 1.2381852551984878 + >>> w.sd() + 1.1127377297451937 + + """ + + def __init__(self, datas = [], data_name = "Valeurs", weights = [], weight_name = "Effectifs"): """ - Initialisation de la série statistique - Les paramètres sont optionnels ils pourront être ajouté après la création de la série - - :param valeurs: valeurs de la série statistique - :param val_name: nom pour les "valeurs" - :param effectifs: effectifs de chaque - :param eff_name: nom pour les "effectifs" - :param random: taille de série si elle doit être initialisé aléatoirement (uniformement sur [0,1] + Initiate the WeightedDataset """ - self.set_serie = False - if valeurs: - self.set_values(valeurs, effectifs) - - self.val_name = val_name - self.eff_name = eff_name - - def set_values(self, valeurs, effectifs = None): - """ - Effecte les valeurs de la série statistique - /!\ mettre les valeurs dans l'ordre croissant si elles sont pondérées!! - - :param valeurs: valeurs de la série statistique - :param effectifs: effectifs de chaque - """ - if not effectifs: - effectifs = [1]*len(valeurs) - valeurs.sort() - - if len(valeurs) != len(effectifs): - raise ValueError("Valeurs et effectifs ne sont pas de la même longueur") - - dict_tmp = [(v,effectifs[i]) for (i,v) in enumerate(valeurs)] - - self.serie = dict(dict_tmp) - self.valeurs = valeurs - self.effectifs = effectifs - self.effectif_total = sum(self.effectifs) - - # On classe les données dans un dictionnaire - # Il faut plutot utiliser un dictionnaire car il trie automatiquement dans l'ordre croissant les clés. - self.serie = {} - for (i,v) in enumerate(self.valeurs): - if v in self.serie.keys(): - self.serie[v] += self.effectifs[i] + if datas and not weights: + weightedDatas = Counter(datas) + elif datas and weights: + if len(datas) != len(weights): + raise ValueError("Datas and weights should have same length") else: - self.serie[v] = self.effectifs[i] + weightedDatas = {i[0]:i[1] for i in zip(datas, weights)} - # On garde une forme (valeur, effectif) pour pouvoir trier les éléments car les dictionnaires ne le permettent pas) - self.serieCouple = [(v,e) for (v,e) in self.serie.items()] - self.serieCouple.sort(key = lambda s:s[0]) + dict.__init__(self, weightedDatas) - self.set_serie = True - - def moyenne(self): - """ - Calcul la moyenne des valeurs pondérées par les effectifs (s'ils ont été passé en arguments) - - :return: renvoie la moyenne. - """ - if self.set_serie: - val_eff = [i*self.serie[i] for i in self.serie] - return sum(val_eff) / self.effectif_total - else: - raise ValueError("La série statistique n'a pas été rentrée") + self.data_name = data_name + self.weight_name = weight_name + def add_data(self, data, weight = 1): + try: + self[data] += weight + except KeyError: + self[data] = weight + + def total_weight(self): + return sum(self.values()) + + def effectif_total(self): + return self.total_weight() + + def sum(self): + """ Not really a sum but the sum of the product of key and values """ + return sum([k*v for (k,v) in self.items()]) + + def mean(self): + return self.sum()/self.effectif_total() + + def deviation(self): + """ Compute the deviation (not normalized) """ + mean = self.mean() + return sum([v*(k - mean)**2 for (k,v) in self.items()]) + def variance(self): - """ - Calcul la variance des valeurs pondérées par les effectifs (s'ils ont été passé en arguments) + return self.deviation()/self.effectif_total() - :return: renvoie la variance - """ - moy = self.moyenne() - ecart = [self.serie[v] * (v - moy)**2 for v in self.serie.keys()] - - return sum(ecart) / self.effectif_total - - def ecart_type(self): - """ - Calcul l'écart type des valeurs pondérées par les effectifs (s'ils ont été passé en arguments) - - :return: renvoie l'écart-type - """ + def sd(self): + """ Compute the standard deviation """ return sqrt(self.variance()) def quartiles(self): @@ -103,8 +87,18 @@ class Serie(): Calcul les quartiles de la série. :return: un tuple avec (min, Q1, Me, Q3, Max) + + : Exemple: + + >>> w = WeightedDataset(flatten_list([i*[i] for i in range(5)])) + >>> w.quartiles() + (1, 2, 3.0, 4, 4) + >>> w = WeightedDataset(flatten_list([i*[i] for i in range(6)])) + >>> w.quartiles() + (1, 3, 4, 5, 5) + """ - return (min(self.serie) , self.quartile(1) , self.quartile(2) , self.quartile(3), max(self.serie)) + return (min(self.keys()) , self.quartile(1) , self.quartile(2) , self.quartile(3), max(self.keys())) def quartile(self, quartile = 1): """ @@ -116,21 +110,29 @@ class Serie(): : Example: - >>> s = Serie() - >>> + >>> w = WeightedDataset(flatten_list([i*[i] for i in range(5)])) + >>> w.quartile(1) + 2 + >>> w.quartile(2) + 3.0 + >>> w.quartile(3) + 4 + >>> w = WeightedDataset(flatten_list([i*[i] for i in range(6)])) + >>> w.quartile(1) + 3 + >>> w.quartile(2) + 4 + >>> w.quartile(3) + 5 """ - position = self.posi_quartile(quartile)[0] - - conteur = 0 - # on utilise la forme avec les couples pour avoir des valeurs classées. - for (val , eff) in self.serieCouple: - conteur += eff - if conteur >= position: - val_quartile = val - break - - return val_quartile + # -1 to match with list indexing + position = self.posi_quartile(quartile) - 1 + expanded_values = flatten_list([v*[k] for (k,v) in self.items()]) + if position.is_integer(): + return (expanded_values[int(position)] + expanded_values[int(position)+1])/2 + else: + return expanded_values[ceil(position)] def posi_quartile(self, quartile = 1): """ @@ -140,121 +142,12 @@ class Serie(): :return : la position du quartile (arondis à l'entier suppérieur, non arrondis) """ - return (ceil(quartile * self.effectif_total / 4), (quartile * self.effectif_total / 4)) + return quartile * self.effectif_total() / 4 # -------------------------- # Rendu latex - def moyenne_latex(self, val_cara = "x", eff_cara = "n", end_cara = "p", moy_cara= "\\bar{x}"): - """ - Renvoie le code latex pour afficher le calcul de la moyenne - - :param val_cara: caractère représentant les valeurs (x par défaut) - :param eff_cara: caractère représentant les effectifs (n par défaut) - :param end_cara: caractère représentant le nombre de valeurs (p par défaut) - :param moy_cara: nom de la moyenne (\\bar{x} par défaut) - - :return: le code latex pour afficher le calcul de la moyenne - """ - moy = self.moyenne() - latex = moy_cara + " &=& " - latex += "\\frac{{{x}_1 \\times {n}_1 + {x}_2 \\times {n}_2 + ... + {x}_{p} \\times {n}_{p}}}{{{n}_1 + {n}_2 + ... + {n}_p}} \\\\ \n".format(x = val_cara, n=eff_cara, p = end_cara) - latex += "\t &=& \\frac{" - for (i,v) in enumerate(self.valeurs): - latex += "{x:.2f} \\times {p:.2f} + ".format(x = v, p = self.effectifs[i]) - latex = latex[:-2] # on enlève le + et l'espace de fin de calcul - latex += "}}{{{eff_tot}}}\\\\ \n".format(eff_tot = self.effectif_total) - latex += "\t &=& {moy:.2f}".format(moy=moy) - - return latex - - def variance_latex(self, val_cara = "x", eff_cara = "n", end_cara = "p", moy_cara= "\\bar{x}", var_cara = "V"): - """ - Renvoie le code latex pour afficher le calcul de la moyenne - - :param val_cara: caractère représentant les valeurs (x par défaut) - :param eff_cara: caractère représentant les effectifs (n par défaut) - :param end_cara: caractère représentant le nombre de valeurs (p par défaut) - :param moy_cara: nom de la moyenne (\\bar{x} par défaut) - :param var_var: nom de la variance (V par défaut) - - :return: le code latex pour afficher le calcul de la moyenne - """ - moy = self.moyenne() - var = self.variance() - - latex = var_cara + " &=& " - latex += "\\frac{{ {n}_1 ({x}_1 - {moy})^2 + {n}_2 ({x}_2 - {moy})^2 + ... + {n}_{p} ({x}_{p} - {moy})^2}}{{{n}_1 + {n}_2 + ... + {n}_p}}\\\\ \n".format(x = val_cara, n=eff_cara, p = end_cara, moy = moy_cara) - latex += "\t &=& \\frac{ " - for (i,v) in enumerate(self.valeurs): - latex += "{p:.2f} ({x:.2f} - {moy:.2f})**2 + ".format(p = self.effectifs[i], x = v, moy = moy) - latex = latex[:-2] # on enlève le + et l'espace de fin de calcul - latex += "}}{{{eff_tot}}}\\\\ \n".format(eff_tot = self.effectif_total) - latex += "\t &=& {var:.2f}".format(var = var) - - return latex - - def ecart_type_latex(self, val_cara = "x", eff_cara = "n", end_cara = "p", moy_cara= "\\bar{x}", var_cara = "V", ecar_cara = "\\sigma"): - """ - Renvoie le code latex pour afficher le calcul de la moyenne - - :param val_cara: caractère représentant les valeurs (x par défaut) - :param eff_cara: caractère représentant les effectifs (n par défaut) - :param end_cara: caractère représentant le nombre de valeurs (p par défaut) - :param moy_cara: nom de la moyenne (\\bar{x} par défaut) - :param var_var: nom de la variance (V par défaut) - :param ecar_var: nom de la variance (V par défaut) - - :return: le code latex pour afficher le calcul de la moyenne - """ - ecart = self.ecart_type() - - # On récupère le calcul de la variance - latex = self.variance_latex(val_cara, eff_cara, end_cara, moy_cara, var_cara) - latex += "\n\n" - - # On y ajoute celui de l'écart-type (ya un soucis ici à cause du conflit entre format et { - latex += "{ecar_cara} &=& \\sqrt{{{var_cara}}} \\\\ \n".format(ecar_cara = ecar_cara, var_cara = var_cara) - latex += "\t &=& {ecart:.2f}".format(ecart = ecart) - - return latex - - def quartiles_latex(self): - """ Renvoie le code latex pour afficher le calcul des quartiles - - :return : le code latex pour afficher le calcul des quartiles - """ - latex = self.quartile_latex() - latex += self.quartile_latex(1) - latex += self.quartile_latex(3) - - return latex - - def quartile_latex(self, quartile = 2): - """ Renvoie le code latex pour afficher le calcul du quartile - - :param quartile: numéro du quartile - - :return : le code latex pour afficher le calcul du quartile - """ - if quartile == 2 : # médiane - quartile_tpl = """ Position de la médiane: $\\dfrac{{\\mbox{{effectif total}}}}{{2}} = \\dfrac{{{eff_tot}}}{{2}} = {posi_q}$. Donc la médiane se trouve à la position: {posi_q_ceil}. - -On a ainsi $Me = {val_q}$ - """ - else: - quartile_tpl = """ Position de $Q_{q}$: $\\dfrac{{{q} \\times \\mbox{{effectif total}}}}{{4}} = \\dfrac{{{q} \\times {eff_tot}}}{{4}} = {posi_q}$. Donc $Q_{q}$ se trouve à la position: {posi_q_ceil}. - -On a ainsi $Q_{q} = {val_q}$ - """ - posi_q_ceil , posi_q = self.posi_quartile(quartile) - val_q = self.quartile(quartile) - - latex = quartile_tpl.format(eff_tot = self.effectif_total, q = quartile, posi_q = posi_q, posi_q_ceil = posi_q_ceil, val_q = val_q) - - return latex - def tabular_latex(self): """ Renvoie le code latex pour afficher le tableau