Анализ данных на предмет несовместимого форматирования строк - PullRequest
5 голосов
/ 23 января 2012

У меня есть задание, над которым я работаю, но я испытываю крайние опасения по поводу моей методологии.

Итак, проблема в том, что у меня есть тонна файлов Excel, которые странно (и не всегда) отформатированы, и мне нужно извлечь определенные поля для каждой записи. Пример набора данных:

a look at the data

Мой оригинальный подход был такой:

  1. Экспорт в CSV
  2. Разделить на графства
  3. Разделить на районы
  4. Анализировать каждый район отдельно, извлекать значения
  5. запись в output.csv

Проблема, с которой я столкнулся, заключается в том, что формат (казалось бы, хорошо организованный) является почти случайным для файлов. Каждая строка содержит одинаковые поля, но в другом порядке, интервале и формулировке. Я написал скрипт для правильной обработки одного файла, но он не работает с другими файлами.

Итак, мой вопрос: есть ли более надежный метод решения этой проблемы, чем простая обработка строк? Я имел в виду нечеткий логический подход к попытке определить, какое поле было у элемента, которое могло бы обрабатывать входные данные, будучи немного произвольными. Как бы вы подошли к этой проблеме?

Если это поможет решить проблему, вот сценарий, который я написал:

# 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)

1 Ответ

3 голосов
/ 23 января 2012

существует ли более надежный метод решения этой проблемы, нежели простая обработка строк?

Не совсем.

Я имел в виду большеподхода нечеткой логики для попытки определить, какое поле было у элемента, которое могло бы обрабатывать входные данные, которые были немного произвольными.Как бы вы подошли к этой проблеме?

После тонны анализа и программирования она не будет значительно лучше, чем у вас.

Для чтения материалов, подготовленных людьми, требуется- к сожалению - мозги, похожие на людей.

Вы можете связываться с NLTK, чтобы попытаться сделать лучше, но это тоже не очень хорошо работает.

Вы не делаетенужен принципиально новый подход.Вам необходимо упростить подход, который у вас есть.

Например.

  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

Может быть улучшен с помощью именованного кортежа.

Затем построить что-то вроде этого.

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

Так что вы не создаете много промежуточных (и в значительной степени неинформативных) свободных переменных.

Кроме того, продолжайте смотреть на вашу analyze функцию (и любую другую функцию), чтобы тянутьиз различных правил "сопоставления с образцом".Идея состоит в том, что вы изучите данные округа, пройдитесь по ряду функций, пока одна из них не будет соответствовать шаблону;это также создаст именованный кортеж.Вы хотите что-то вроде этого.

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

Каждая функция возвращает именованный кортеж (потому что ей понравилась строка) или None (потому что ей не понравилась строка).

...