2010-10-27 13 views
8

Je suis en train de traiter un fichier CSV et de compter les valeurs uniques de la colonne 4. Jusqu'à présent, j'ai codé ces trois façons. L'un utilise "if key in dictionary", le second piège KeyError et le troisième utilise "DefaultDictionary". Par exemple (où x [3] est la valeur du fichier et "a" est un dictionnaire):Ajouter de nouvelles clés à un dictionnaire tout en incrémentant des valeurs existantes

Première façon:

if x[3] in a: 
    a[x[3]] += 1 
else: 
    a[x[3]] = 1 

Deuxième moyen:

try: 
    b[x[3]] += 1 
except KeyError: 
    b[x[3]] = 1 

Troisième voie: Ma question est la suivante: quel est le moyen le plus efficace ... nettoyant ... mieux ... etc. Ou y a-t-il une meilleure façon de procéder? Les deux façons de travailler et de donner la même réponse, mais je pensais que je voudrais exploiter l'esprit de la ruche comme un cas d'apprentissage.

Merci -

+0

Édition supplémentaire: Je cours la version 2.7. Aurait dû l'ajouter plus tôt! –

+0

'Counter' ** est plus lent ** mais il a beaucoup plus de fonctionnalités que' defaultdict (int) '- voir ma réponse. –

+0

pouvez-vous incrémenter plusieurs valeurs? – Mohsin

Répondre

6

Utilisez collections.Counter. Counter est le sucre syntaxique pour defaultdict(int), mais ce qui est cool à ce sujet est qu'il accepte un itérables dans le constructeur, économisant ainsi une étape supplémentaire (je suppose que tous vos exemples ci-dessus sont enveloppés dans une boucle for.)

from collections import Counter 
count = Counter(x[3] for x in my_csv_reader) 

Avant l'introduction de collections.Counter, collections.defaultdict était le plus idiomatique pour cette tâche, donc pour les utilisateurs < 2.7, utilisez defaultdict.

from collections import defaultdict 
count = defaultdict(int) 
for x in my_csv_reader: 
    count[x[3]] += 1 
+0

Je vais réclamer le statut de débutant sur cette clarification. Est-ce que le "x" dans la partie "pour x" se rapporte à la ligne individuelle retournée par le csv.reader? Je "pense" que j'ai mis en œuvre le lecteur csv: my_csv_reader = csv.reader (open ('c: /Test.csv', 'rb'), délimiteur = ',', quotechar = '|') d = Compteur (x [3] pour x dans mon_sv_reader) mais d est plus de deux fois plus grand que les trois autres méthodes. –

+0

Oui, 'x' fait référence à la ligne renvoyée par le lecteur csv. Que voulez-vous dire par d étant plus de deux fois plus grand? Est-ce que ça donne les mauvais comptes? Précisez s'il vous plaît. –

+0

Clarification: Mon code est incorrect et ma question n'était pas complète! Il existe une instruction if entourant les trois premières méthodes qui ont supprimé les valeurs en double en faisant correspondre la ligne en cours avec la ligne précédente avant que la valeur ne soit ajoutée au dictionnaire. La nouvelle méthode n'est pas dans cette condition, donc j'ai obtenu un total de toutes les lignes, pas les uniques! –

1
from collections import Counter 
Counter(a) 
+0

"a" est mon dictionnaire. Est-ce que "Counter (x [3])? –

+0

" Counter "n'est pas nouveau dans 3.1? Je cours 2.7 –

+0

@Count: [il existe dans 2.7] (http://docs.python.org/library /collections.html#collections.Counter) – SilentGhost

0

Puisque vous n'avez pas accès à Counter, votre meilleur pari est votre troisième approche. C'est beaucoup plus propre et plus facile à lire. En outre, il n'a pas le test perpétuel (et ramification) que les deux premières approches ont, ce qui le rend plus efficace.

6

Vous avez demandé lequel était le plus efficace. En supposant que vous parlez de la vitesse d'exécution: Si vos données sont petites, cela n'a pas d'importance. S'il est grand et typique, le cas "déjà existant" se produira beaucoup plus souvent que le cas "non existant". Cette observation explique certains des résultats. Vous trouverez ci-dessous un code pouvant être utilisé avec le module timeit pour explorer la vitesse sans surcharge de lecture de fichier. J'ai pris la liberté d'ajouter une 5ème méthode, qui n'est pas non-compétitive et fonctionnera sur n'importe quel Python à partir d'au moins 1.5.2 [testé].

from collections import defaultdict, Counter 

def tally0(iterable): 
    # DOESN'T WORK -- common base case for timing 
    d = {} 
    for item in iterable: 
     d[item] = 1 
    return d 

def tally1(iterable): 
    d = {} 
    for item in iterable: 
     if item in d: 
      d[item] += 1 
     else: 
      d[item] = 1 
    return d 

def tally2(iterable): 
    d = {} 
    for item in iterable: 
     try: 
      d[item] += 1 
     except KeyError: 
      d[item] = 1 
    return d 

def tally3(iterable): 
    d = defaultdict(int) 
    for item in iterable: 
     d[item] += 1 

def tally4(iterable): 
    d = Counter() 
    for item in iterable: 
     d[item] += 1 

def tally5(iterable): 
    d = {} 
    dg = d.get 
    for item in iterable: 
     d[item] = dg(item, 0) + 1 
    return d 

course typique (dans la fenêtre Windows XP "Invite de commandes"):

prompt>\python27\python -mtimeit -s"t=1000*'now is the winter of our discontent made glorious summer by this son of york';import tally_bench as tb" "tb.tally1(t)" 
10 loops, best of 3: 29.5 msec per loop 

Voici les résultats (msec par boucle):

0 base case 13.6 
1 if k in d 29.5 
2 try/except 26.1 
3 defaultdict 23.4 
4 Counter  79.4 
5 d.get(k, 0) 29.2 

Un autre essai de synchronisation:

prompt>\python27\python -mtimeit -s"from collections import defaultdict;d=defaultdict(int)" "d[1]+=1" 
1000000 loops, best of 3: 0.309 usec per loop 

prompt>\python27\python -mtimeit -s"from collections import Counter;d=Counter()" "d[1]+=1" 
1000000 loops, best of 3: 1.02 usec per loop 

La vitesse de Counter est probablement due à son implémentation partielle en code Python alors que defaultdict est entièrement en C (en 2.7, au moins).

Notez que Counter() est pas seulement « sucre syntaxique » pour defaultdict(int) - il met en œuvre une bag complète alias multiset objet - voir la documentation pour plus de détails; ils peuvent vous éviter de réinventer la roue si vous avez besoin d'un post-traitement de fantaisie. Si tout ce que vous voulez faire est de compter les choses, utilisez defaultdict.

Mise à jour en réponse à une question de @Steven Rumbalski: « » » Je suis curieux, ce qui se passe si vous déplacez le itérables dans le constructeur du compteur: d = Compteur (itérables) (je python 2.6 et ne peut pas le tester) « » »

tally6:. d = Count(iterable); return d fait exactement, prend 60,0 msec

Vous pouvez regarder la source (collections.py dans le dépôt SVN) ... voici ce que mon Python27\Lib\collections.py fait quand iterable n'est pas une instance de mappage:

  self_get = self.get 
      for elem in iterable: 
       self[elem] = self_get(elem, 0) + 1 

Vous avez déjà vu ce code auparavant? Il y a beaucoup de bagage à main pour appeler du code exécutable en Python 1.5.2 :-O

+0

Bons moments.Je suis curieux, que se passe-t-il si vous déplacez le itérable dans le constructeur du compteur: 'd = Counter (itérable)'? (J'ai python 2.6 et je ne peux pas le tester.) –

+0

@Steven Rumbalski: voir ma réponse mise à jour. –

0

Utilisez setdefault.

a[x[3]] = a.setdefault(x[3], 0) + 1 

setdefault obtient la valeur de la clé spécifiée (x[3] dans ce cas), ou si elle n'existe pas, la valeur spécifiée (0 dans ce cas).

+0

Veuillez expliquer pourquoi vous pensez que 'd [k] = d.setdefault (k, 0) + 1' serait meilleur que' d [k] = d.get (k, 0) + 1' –

+0

Oui, vous êtes droite. Je pensais à cela parce que cela définirait la valeur, mais vous le faites de toute façon. – kindall