2012-01-23 10 views
5

Tengo esta tarea en la que he estado trabajando, pero estoy teniendo dudas sobre mi metodología.Análisis de datos para el formato de cadena incoherente

Así que el problema es que tengo un montón de archivos de Excel formateados de forma extraña (y no siempre) y necesito extraer ciertos campos para cada entrada. Un conjunto de datos de ejemplo es

a look at the data

Mi enfoque original era la siguiente:

  1. Exportar a CSV
  2. independiente en condados
  3. separar en los distritos
  4. analizar cada distrito individual, tire valores
  5. escribir en output.csv

El problema que me he encontrado es que el formato (aparentemente bien organizado) es casi aleatorio en todos los archivos. Cada línea contiene los mismos campos, pero en un orden diferente, espaciado y redacción. Escribí un script para procesar correctamente un archivo, pero no funciona en ningún otro archivo.

Así que mi pregunta es, ¿hay un método más sólido para abordar este problema en lugar de un simple procesamiento de cadenas? Lo que tenía en mente era más bien un enfoque de lógica difusa para tratar de determinar qué campo era un elemento, lo que podría manejar las entradas siendo un poco arbitrarias. ¿Cómo abordarías este problema?

Si ayuda a aclarar el problema, aquí es el guión que escribí:

# This file takes a tax CSV file as input 
# and separates it into counties 
# then appends each county's entries onto 
# the end of the master out.csv 
# which will contain everything including 
# taxes, bonds, etc from all years 

#import the data csv 
import sys 
import re 
import csv 

def cleancommas(x): 
    toggle=False 
    for i,j in enumerate(x): 
    if j=="\"": 
     toggle=not toggle 
    if toggle==True: 
     if j==",": 
     x=x[:i]+" "+x[i+1:] 
    return x 

def districtatize(x): 
    #list indexes of entries starting with "for" or "to" of length >5 
    indices=[1] 
    for i,j in enumerate(x): 
    if len(j)>2: 
     if j[:2]=="to": 
     indices.append(i) 
    if len(j)>3: 
     if j[:3]==" to" or j[:3]=="for": 
     indices.append(i) 
    if len(j)>5: 
     if j[:5]==" \"for" or j[:5]==" \'for": 
     indices.append(i) 
    if len(j)>4: 
     if j[:4]==" \"to" or j[:4]==" \'to" or j[:4]==" for": 
     indices.append(i) 
    if len(indices)==1: 
    return [x[0],x[1:len(x)-1]] 
    new=[x[0],x[1:indices[1]+1]] 
    z=1 
    while z<len(indices)-1: 
    new.append(x[indices[z]+1:indices[z+1]+1]) 
    z+=1 
    return new 
    #should return a list of lists. First entry will be county 
    #each successive element in list will be list by district 

def splitforstos(string): 
    for itemind,item in enumerate(string):  # take all exception cases that didn't get processed 
    splitfor=re.split('(?<=\d)\s\s(?=for)',item) # correctly and split them up so that the for begins 
    splitto=re.split('(?<=\d)\s\s(?=to)',item) # a cell 
    if len(splitfor)>1: 
     print "\n\n\nfor detected\n\n" 
     string.remove(item) 
     string.insert(itemind,splitfor[0]) 
     string.insert(itemind+1,splitfor[1]) 
    elif len(splitto)>1: 
     print "\n\n\nto detected\n\n" 
     string.remove(item) 
     string.insert(itemind,splitto[0]) 
     string.insert(itemind+1,splitto[1]) 

def analyze(x): 
    #input should be a string of content 
    #target values are nomills,levytype,term,yearcom,yeardue 
    clean=cleancommas(x) 
    countylist=clean.split(',') 
    emptystrip=filter(lambda a: a != '',countylist) 
    empt2strip=filter(lambda a: a != ' ', emptystrip) 
    singstrip=filter(lambda a: a != '\' \'',empt2strip) 
    quotestrip=filter(lambda a: a !='\" \"',singstrip) 
    splitforstos(quotestrip) 
    distd=districtatize(quotestrip) 
    print '\n\ndistrictized\n\n',distd 
    county = distd[0] 
    for x in distd[1:]: 
    if len(x)>8: 
     district=x[0] 
     vote1=x[1] 
     votemil=x[2] 
     spaceindex=[m.start() for m in re.finditer(' ', votemil)][-1] 
     vote2=votemil[:spaceindex] 
     mills=votemil[spaceindex+1:] 
     votetype=x[4] 
     numyears=x[6] 
     yearcom=x[8] 
     yeardue=x[10] 
     reason=x[11] 
     data = [filename,county,district, vote1, vote2, mills, votetype, numyears, yearcom, yeardue, reason] 
     print "data",data 
    else: 
     print "x\n\n",x 
     district=x[0] 
     vote1=x[1] 
     votemil=x[2] 
     spaceindex=[m.start() for m in re.finditer(' ', votemil)][-1] 
     vote2=votemil[:spaceindex] 
     mills=votemil[spaceindex+1:] 
     votetype=x[4] 
     special=x[5] 
     splitspec=special.split(' ') 
     try: 
     forind=[i for i,j in enumerate(splitspec) if j=='for'][0] 
     numyears=splitspec[forind+1] 
     yearcom=splitspec[forind+6] 
     except: 
     forind=[i for i,j in enumerate(splitspec) if j=='commencing'][0] 
     numyears=None 
     yearcom=splitspec[forind+2] 
     yeardue=str(x[6])[-4:] 
     reason=x[7] 
     data = [filename,county,district,vote1,vote2,mills,votetype,numyears,yearcom,yeardue,reason] 
     print "data other", data 
    openfile=csv.writer(open('out.csv','a'),delimiter=',', quotechar='|',quoting=csv.QUOTE_MINIMAL) 
    openfile.writerow(data) 

# call the file like so: python tax.py 2007May8Tax.csv 
filename = sys.argv[1] #the file is the first argument 
f=open(filename,'r') 
contents=f.read() #entire csv as string 
#find index of every instance of the word county 
separators=[m.start() for m in re.finditer('\w+\sCOUNTY',contents)] #alternative implementation in regex 

# split contents into sections by county 
# analyze each section and append to out.csv 
for x,y in enumerate(separators): 
    try: 
    data = contents[y:separators[x+1]] 
    except: 
    data = contents[y:] 
    analyze(data) 
+7

La mejor y más confiable manera de resolver esto es forzar a las personas que le proporcionan estos datos a seguir un formato estándar, pero no sé si esa es una opción disponible para usted. Sin imponer ninguna estructura, las personas que proporcionan estos datos romperán constantemente su programa. –

Respuesta

3

¿Hay un método más robusto de abordar este problema en lugar de procesamiento de cadenas simples?

Realmente no.

Lo que tenía en mente era más bien un enfoque de lógica difusa para tratar de señalar qué campo era un elemento, lo que podría manejar las entradas siendo un poco arbitrarias. ¿Cómo abordarías este problema?

Después de un montón de análisis y programación, no será significativamente mejor que lo que tienes.

Leer cosas preparadas por personas requiere, por desgracia, cerebros similares a personas.

Puede jugar con NLTK para intentar hacer un mejor trabajo, pero tampoco funciona demasiado bien.

No necesita un enfoque radicalmente nuevo. Necesita simplificar el enfoque que tiene.

Por ejemplo.

district=x[0] 
    vote1=x[1] 
    votemil=x[2] 
    spaceindex=[m.start() for m in re.finditer(' ', votemil)][-1] 
    vote2=votemil[:spaceindex] 
    mills=votemil[spaceindex+1:] 
    votetype=x[4] 
    numyears=x[6] 
    yearcom=x[8] 
    yeardue=x[10] 
    reason=x[11] 
    data = [filename,county,district, vote1, vote2, mills, votetype, numyears, yearcom, yeardue, reason] 
    print "data",data 

Se puede mejorar utilizando una tupla nombrada.

Luego crea algo como esto.

data = SomeSensibleName( 
    district= x[0], 
    vote1=x[1], ... etc. 
) 

Para que no esté creando muchas variables sueltas intermedias (y poco informativas).

Además, sigue mirando la función analyze (y cualquier otra función) para extraer las diversas reglas de "coincidencia de patrones". La idea es que examinará los datos de un condado, pasará por un conjunto de funciones hasta que coincida con el patrón; esto también creará la tupla nombrada. Quieres algo como esto.

for p in (some, list, of, functions): 
    match= p(data) 
    if match: 
     return match 

Cada función o bien devuelve una tupla con nombre (porque le gusta la fila) o None (porque no le gustaba la fila).

Cuestiones relacionadas