Как правильно прочитать файл 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(';')]