Как правильно прочитать файл csv, если каждая строка содержит разное количество полей (число довольно большое)?

у меня есть текстовый файл от amazon, содержащий следующую информацию:

 #      user        item     time   rating     review text (the header is added by me for explanation, not in the text file
  disjiad123    TYh23hs9     13160032    5     I love this phone as it is easy to use
  hjf2329ccc    TGjsk123     14423321    3     Suck restaurant

как вы видите, данные разделены пробелом, и в каждой строке есть разное количество столбцов. Тем не менее, это текстовое содержание. Вот код, который я пробовал:

pd.read_csv(filename, sep = " ", header = None, names = ["user","item","time","rating", "review"], usecols = ["user", "item", "rating"])#I'd like to skip the text review part

и возникает такая ошибка:

ValueError: Passed header names mismatches usecols

когда я попытался прочитать все столбцы:

pd.read_csv(filename, sep = " ", header = None)

и ошибка на этот раз:

Error tokenizing data. C error: Expected 229 fields in line 3, saw 320

и учитывая текст обзора так долго во многих строках метод добавления имен заголовков для каждого столбца в этом вопрос не может работать.

интересно, как читать csv-файл, если я хочу сохранить текст обзора и пропустить их соответственно. Заранее спасибо!

EDIT:

проблема была решена Мартином Эвансом отлично. Но теперь я играю с другим набором данных с аналогичным, но другим форматом. Теперь порядок данных converse:

     # review text                          user        item     time   rating      (the header is added by me for explanation, not in the text file
   I love this phone as it is easy to used  isjiad123    TYh23hs9     13160032    5    
  Suck restaurant                           hjf2329ccc    TGjsk123     14423321    3     

у вас есть идея прочитать его правильно? Будем признательны за любую помощь!

6 ответов


как и предлагалось,DictReader может также использоваться следующим образом для создания списка строк. Затем это может быть импортировано как кадр в pandas:

import pandas as pd
import csv

rows = []
csv_header = ['user', 'item', 'time', 'rating', 'review']
frame_header = ['user', 'item', 'rating', 'review']

with open('input.csv', 'rb') as f_input:
    for row in csv.DictReader(f_input, delimiter=' ', fieldnames=csv_header[:-1], restkey=csv_header[-1], skipinitialspace=True):
        try:
            rows.append([row['user'], row['item'], row['rating'], ' '.join(row['review'])])
        except KeyError, e:
            rows.append([row['user'], row['item'], row['rating'], ' '])

frame = pd.DataFrame(rows, columns=frame_header)
print frame

это будет выглядеть следующим образом:

         user      item rating                                  review
0  disjiad123  TYh23hs9      5  I love this phone as it is easy to use
1  hjf2329ccc  TGjsk123      3                         Suck restaurant

если обзор появляется в начале строки, то одним из подходов будет разбор строки в обратном порядке следующим образом:

import pandas as pd
import csv


rows = []
frame_header = ['rating', 'time', 'item', 'user', 'review']

with open('input.csv', 'rb') as f_input:
    for row in f_input:
        cols = [col[::-1] for col in row[::-1][2:].split(' ') if len(col)]
        rows.append(cols[:4] + [' '.join(cols[4:][::-1])])

frame = pd.DataFrame(rows, columns=frame_header)
print frame

это будет отображаться:

  rating      time      item        user  \
0      5  13160032  TYh23hs9   isjiad123   
1      3  14423321  TGjsk123  hjf2329ccc   

                                    review  
0  I love this phone as it is easy to used  
1                          Suck restaurant  

row[::-1] используется для обратного текста всей строки,[2:] пропускает окончание строки, которая теперь находится в начале строки. Затем каждая строка разбивается на пробелы. Понимание списка затем реверсирует каждую разделенную запись. Наконец rows добавляется к первому, принимая фиксированные 5 записей столбца (теперь в начале). Остальные записи затем объединяются вместе с пробелом и добавляются в качестве последнего столбца.

преимущество этого подхода заключается в том, что он не полагается на ваши входные данные в точно фиксированном формате ширины, и у вас нет беспокоиться, если используемые ширины столбцов изменяются с течением времени.


похоже, что это файл фиксированной ширины. Панды поставляет read_fwf для достижения этой цели. Следующий код читает файл правильно для меня. Вы можете немного повозиться с шириной, если она не работает идеально.

pandas.read_fwf('test.fwf', 
                 widths=[13, 12, 13, 5, 100], 
                 names=['user', 'item', 'time', 'rating', 'review'])

если столбцы по-прежнему совпадают с отредактированной версией (где рейтинг идет первым), вам просто нужно добавить правильную спецификацию. Направляющая линия, как показано ниже, помогает сделать это быстро:

0        1         2         3         4         5         6         7         8
123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
  I love this phone as it is easy to used  isjiad123    TYh23hs9     13160032    5    
  Suck restaurant                          hjf2329ccc   TGjsk123     14423321    3     

таким образом, новая команда становится:

pandas.read_fwf('test.fwf', 
                colspecs=[[0, 43], [44, 56], [57, 69], [70, 79], [80, 84]], 
                names=['review', 'user', 'item', 'time', 'rating'])

Usecols ссылается на имена столбцов во входном файле. Если в вашем файле нет таких столбцов (user, item, rating) не знаю, какие столбцы вы имеете в виду. Вместо этого вы должны передать указатель как usecols=[0,1,2].

и names относится к тому, что вы называете импортируемыми столбцами. Итак, я думаю, вы не можете иметь четыре имени при импорте 3 столбцов. Это работает?

pd.read_csv(filename, sep = " ", 
                      header = None, 
                      names = ["user","item","rating"], 
                      usecols = [0,1,2])

ошибка разбора выглядит как проблема с разделителем. Это может попробуйте разобрать ваш review text столбец столько столбцов, потому что "я" "люблю" "это" ... все разделены пробелами. Надеюсь, если Вы читаете только первые три столбца, вы можете избежать ошибки, но если нет, вы можете рассмотреть разбор строки за строкой (например, здесь: http://cmdlinetips.com/2011/08/three-ways-to-read-a-text-file-line-by-line-in-python/) и запись в фрейм данных оттуда.


Я думаю, что лучший подход-использовать pandas read_csv:

 import pandas as pd
import io

temp=u"""  disjiad123    TYh23hs9     13160032    5     I love this phone as it is easy to use
  hjf2329ccc    TGjsk123     14423321    3     Suck restaurant so I love cooking pizza with onion ham garlic tomatoes """


#estimated max length of columns 
N = 20

#after testing replace io.StringIO(temp) to filename
df = pd.read_csv(io.StringIO(temp), 
                 sep = "\s+", #separator is arbitrary whitespace 
                 header = None, #first row is not header, read all data to df
                 names=range(N)) 
print df
           0         1         2   3     4           5     6      7     8   \
0  disjiad123  TYh23hs9  13160032   5     I        love  this  phone    as   
1  hjf2329ccc  TGjsk123  14423321   3  Suck  restaurant    so      I  love   

        9      10    11     12   13      14        15  16  17  18  19  
0       it     is  easy     to  use     NaN       NaN NaN NaN NaN NaN  
1  cooking  pizza  with  onion  ham  garlic  tomatoes NaN NaN NaN NaN

#get order of wanted columns
df = df.iloc[:, [0,1,2]]
#rename columns
df.columns = ['user','item','time']
print df
         user      item      time
0  disjiad123  TYh23hs9  13160032
1  hjf2329ccc  TGjsk123  14423321

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

import pandas as pd
import csv

#preprocessing
def get_max_len():
    with open('file1.csv', 'r') as csvfile:
        reader = csv.reader(csvfile)
        num = []
        for i, row in enumerate(reader):
            num.append(len(''.join(row).split()))
        m = max(num)
        #print m
        return m


df = pd.read_csv('file1.csv', 
                         sep = "\s+", #separator is arbitrary whitespace 
                         header = None, #first row is not header, read all data to df
                         usecols = range(get_max_len())) #filter first, second and fourth column (python count from 0)
print df
           0         1         2   3     4           5     6      7    8   \
0  disjiad123  TYh23hs9  13160032   5     I        love  this  phone   as   
1  hjf2329ccc  TGjsk123  14423321   3  Suck  restaurant   NaN    NaN  NaN   

    9    10    11   12   13  
0   it   is  easy   to  use  
1  NaN  NaN   NaN  NaN  NaN 
#df from 4 col to last
print df.ix[:, 4:]
     4           5     6      7    8    9    10    11   12   13
0     I        love  this  phone   as   it   is  easy   to  use
1  Suck  restaurant   NaN    NaN  NaN  NaN  NaN   NaN  NaN  NaN

#concanecate columns to one review text
df['review text'] = df.ix[:, 4:].apply(lambda x: ' '.join([e for e in x if isinstance(e, basestring)]), axis=1)
df = df.rename(columns={0:'user', 1:'item', 2:'time',3:'rating'})

#get string columns
cols = [x for x in df.columns if isinstance(x, basestring)]

#filter only string columns
print df[cols]
         user      item      time  rating  \
0  disjiad123  TYh23hs9  13160032       5   
1  hjf2329ccc  TGjsk123  14423321       3   

                              review text  
0  I love this phone as it is easy to use  
1                         Suck restaurant  

поскольку первые четыре (теперь последние четыре) поля никогда не будут содержать пробелы или должны быть окружены кавычками, давайте забудем о библиотеке csv и напрямую используем потрясающую обработку строк python. Вот однострочная строка, которая разбивает каждую строку ровно на пять столбцов, любезно предоставлено до rsplit():

with open("myfile.dat") as data:
    frame = pd.DataFrame(line.strip().rsplit(maxsplit=4) for line in data)

выше должно решить вашу проблему, но я предпочитаю, чтобы положить его в функцию генератора, что легче понять, и может быть расширено при необходимости:

def splitfields(data):
    """Generator that parses the data correctly into fields"""
    for line in data:
        fields = line.rsplit(maxsplit=4)
        fields[0] = fields[0].strip()   # trim line-initial spaces
        yield fields

with open("myfile.dat") as data:
    frame = pd.DataFrame(splitfields(data))

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

выше для формата в обновленном вопросе, который имеет свободный текст слева. (Для исходного формата используйте line.split вместо line.rsplit и зачистите поле, не первый.)

    I love this phone as it is easy to used  isjiad123    TYh23hs9     13160032    5    
  Suck restaurant                           hjf2329ccc    TGjsk123     14423321    3

вы можете сделать больше в зависимости от того, как выглядят данные: если поля разделены ровно четырьмя пробелами (как кажется из вашего примера), вы можете разделить их на " " вместо разделения на все пробелы. Это также будет работать правильно, если некоторые другие поля могут содержать пробелы. В общем, предварительный анализ, как это, является гибким и расширяемым; я оставляю код простым, так как нет никаких доказательств из вашего вопроса, Что нужно больше.


Я бы перебирал каждую строку и заменял последовательные пробелы точкой с запятой. Затем позвоните str.split () и выберите точку с запятой в качестве разделителя. Это может выглядеть следующим образом:

data = [["user","item","rating", "review"]]
with open("your.csv") as f:
    for line in f.readlines():
        for i in range(10, 1, -1):
            line = line.replace(' '*i, ';')
        data += [line.split(';')]