2010-10-27 9 views
8

Estoy procesando un archivo CSV y contando los valores únicos de la columna 4. Hasta ahora he codificado esto de tres maneras. Uno usa "si la clave en el diccionario", el segundo atrapa el KeyError y el tercero usa "DefaultDictionary". Por ejemplo (donde x [3] es el valor del archivo y "a" es un diccionario):Agregar nuevas claves a un diccionario mientras se incrementan los valores existentes

Primera forma:

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

Segunda forma:

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

Tercera forma:

from collections import defaultdict 
c = defaultdict(int) 
c[x[3]] += 1 

Mi pregunta es: ¿qué camino es más eficiente ... más limpio ... mejor ... etc. O hay una manera mejor. Ambas formas funcionan y dan la misma respuesta, pero pensé que podría aprovechar la mente de la colmena como un caso de aprendizaje.

Gracias -

+0

Edición adicional: estoy ejecutando la versión 2.7. Debería haber agregado esto antes! –

+0

'Counter' ** es más lento ** pero tiene mucha más funcionalidad que' defaultdict (int) '- ver mi respuesta. –

+0

¿puede incrementar valores múltiples? – Mohsin

Respuesta

6

Use collections.Counter. Counter es el azúcar sintáctica para defaultdict(int), pero lo que es bueno de esto es que se acepta un iterable en el constructor, ahorrando así un paso adicional (supongo que todos sus ejemplos anteriores están envueltos en un bucle para.)

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

Antes de la introducción de collections.Counter, collections.defaultdict era el más idiomático para esta tarea, por lo que para los usuarios < 2.7, usa defaultdict.

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

Voy a reclamar el estado de Novato en esta aclaración. ¿La "x" en la porción "para x" se refiere a la línea individual devuelta por el lector csv? Creo que implementé el lector de csv: my_csv_reader = csv.reader (open ('c: /Test.csv', 'rb'), delimiter = ',', quotechar = '|') d = Contador (x [3] para x en my_csv_reader) pero d es más del doble que los otros tres métodos. –

+0

Sí, 'x' se refiere a la línea devuelta por el lector csv. ¿A qué te refieres con que d es más del doble de grande? ¿Está dando los conteos incorrectos? Por favor aclara –

+0

Aclaración: ¡Mi código es incorrecto y mi pregunta no estaba completa! Hay una instrucción if que rodea a los primeros tres métodos que eliminaron valores duplicados al hacer coincidir la línea actual con la línea anterior antes de que el valor se agregue al diccionario. El nuevo método no está en esa condición, así que obtuve un total de todas las líneas, ¡no las únicas! –

1
from collections import Counter 
Counter(a) 
+0

"a" es mi diccionario. ¿Este es "Contador (x [3])?" –

+0

¿No es "contador" nuevo en 3.1? Estoy ejecutando 2.7. –

+0

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

0

Puesto que usted no tiene acceso a Counter, lo mejor es su tercer enfoque. Es mucho más limpio y fácil de leer. Además, no tiene la prueba perpetua (y ramificación) que tienen los primeros dos enfoques, lo que lo hace más eficiente.

6

Ha preguntado cuál era más eficiente. Suponiendo que está hablando de la velocidad de ejecución: si sus datos son pequeños, no importa. Si es grande y típico, el caso "ya existe" ocurrirá mucho más a menudo que el caso "no está en dict". Esta observación explica algunos de los resultados.

A continuación se muestra un código que se puede utilizar con el módulo timeit para explorar la velocidad sin sobrecarga de lectura de archivos. Me he tomado la libertad de agregar un quinto método, que no es sencillo y se ejecutará en cualquier Python desde al menos 1.5.2 [probado] en adelante.

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 

ejecución típica (en "Símbolo del sistema" Windows XP ventana):

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 

Estos son los resultados (ms por circular):

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 

Otro ensayo de tiempo:

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 velocidad de Counter posiblemente se deba a que se implementa parcialmente en código Python, mientras que defaultdict está completamente en C (en 2.7, como mínimo).

Tenga en cuenta que no es sólo Counter() "azúcar sintáctico" para defaultdict(int) - implementa un completo bag aka multiset objeto - véase la documentación para más detalles; pueden evitar que reinventes la rueda si necesitas un postprocesamiento sofisticado. Si todo lo que quiere hacer es contar cosas, use defaultdict.

actualización en respuesta a una pregunta de @Steven Rumbalski: """ Tengo curiosidad, ¿qué ocurre si se mueve la iterable en el constructor del contador:? D = Contador (iterable) (tengo Python 2.6 y no puede probarlo) """

tally6:. simplemente no d = Count(iterable); return d, toma 60,0 milisegundos

se podría buscar en la fuente (collections.py en el repositorio SVN) ... aquí es lo que mi Python27\Lib\collections.py hace cuando iterable no es una instancia de asignación:

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

¿Has visto ese código en algún lugar antes? Hay un montón de equipaje de mano solo para llamar al código ejecutable en Python 1.5.2 :-O

+0

Tiempos agradables.Tengo curiosidad, ¿qué pasa si mueves el iterable al constructor del contador: 'd = Counter (iterable)'? (Tengo Python 2.6 y no puedo probarlo) –

+0

@Steven Rumbalski: ver mi respuesta actualizada. –

0

Use setdefault.

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

setdefault obtiene el valor de la clave especificada (x[3] en este caso), o si no existe, el valor especificado (0 en este caso).

+0

Explique por qué cree que 'd [k] = d.setdefault (k, 0) + 1' sería mejor que' d [k] = d.get (k, 0) + 1' –

+0

Sí, lo es derecho. Estaba pensando en eso porque establecería el valor, pero lo estás haciendo de todos modos. – kindall

Cuestiones relacionadas