Преобразование текстового документа в специальном формате в Pandas DataFrame
У меня есть текстовый файл в следующем формате:
1: frack 0.733, shale 0.700,
10: space 0.645, station 0.327, nasa 0.258,
4: celebr 0.262, bahar 0.345
Мне нужно преобразовать этот текст в DataFrame в следующем формате:
Id Term weight
1 frack 0.733
1 shale 0.700
10 space 0.645
10 station 0.327
10 nasa 0.258
4 celebr 0.262
4 bahar 0.345
Как я могу это сделать?
Ответы
Ответ 1
Здесь оптимизированный способ синтаксического анализа файла с re
, сначала беря ID и затем анализируя кортежи данных. Это использует тот факт, что файловые объекты являются итеративными. Когда вы перебираете открытый файл, вы получаете отдельные строки в виде строк, из которых вы можете извлечь значимые элементы данных.
import re
import pandas as pd
SEP_RE = re.compile(r":\s+")
DATA_RE = re.compile(r"(?P<term>[a-z]+)\s+(?P<weight>\d+\.\d+)", re.I)
def parse(filepath: str):
def _parse(filepath):
with open(filepath) as f:
for line in f:
id, rest = SEP_RE.split(line, maxsplit=1)
for match in DATA_RE.finditer(rest):
yield [int(id), match["term"], float(match["weight"])]
return list(_parse(filepath))
Пример:
>>> df = pd.DataFrame(parse("/Users/bradsolomon/Downloads/doc.txt"),
... columns=["Id", "Term", "weight"])
>>>
>>> df
Id Term weight
0 1 frack 0.733
1 1 shale 0.700
2 10 space 0.645
3 10 station 0.327
4 10 nasa 0.258
5 4 celebr 0.262
6 4 bahar 0.345
>>> df.dtypes
Id int64
Term object
weight float64
dtype: object
Прохождение
SEP_RE
ищет начальный разделитель: литерал :
сопровождается одним или несколькими пробелами. Он использует maxsplit=1
чтобы остановиться, как только найден первый сплит. Конечно, это предполагает, что ваши данные строго отформатированы: что формат всего набора данных последовательно соответствует формату примера, изложенному в вашем вопросе.
После этого DATA_RE.finditer()
каждую пару (срок, вес), извлеченную из rest
. Строка rest
сама будет выглядеть frack 0.733, shale 0.700,
. .finditer()
предоставляет вам несколько match
объектов, где вы можете использовать нотацию ["key"]
для доступа к элементу из заданной именованной группы захвата, например (?P<term>[az]+)
.
Простой способ визуализировать это - использовать line
примера из вашего файла в виде строки:
>>> line = "1: frack 0.733, shale 0.700,\n"
>>> SEP_RE.split(line, maxsplit=1)
['1', 'frack 0.733, shale 0.700,\n']
Теперь у вас есть начальный идентификатор и остальные компоненты, которые вы можете распаковать в два идентификатора.
>>> id, rest = SEP_RE.split(line, maxsplit=1)
>>> it = DATA_RE.finditer(rest)
>>> match = next(it)
>>> match
<re.Match object; span=(0, 11), match='frack 0.733'>
>>> match["term"]
'frack'
>>> match["weight"]
'0.733'
Лучший способ визуализировать это с помощью pdb
. Попробуйте, если решитесь;)
отказ
Это один из тех вопросов, которые требуют определенного типа решения, которое может не обобщаться, если вы ослабите ограничения на свой формат данных.
Например, предполагается, что каждый Term
может принимать только прописные или строчные буквы ASCII, и ничего больше. Если у вас есть другие символы Unicode в качестве идентификаторов, вы можете рассмотреть другие re
символы, такие как \w
.
Ответ 2
Вы можете использовать конструктор DataFrame, если вы массируете ввод в соответствующий формат. Вот один из способов:
import pandas as pd
from itertools import chain
text="""1: frack 0.733, shale 0.700,
10: space 0.645, station 0.327, nasa 0.258,
4: celebr 0.262, bahar 0.345 """
df = pd.DataFrame(
list(
chain.from_iterable(
map(lambda z: (y[0], *z.strip().split()), y[1].split(",")) for y in
map(lambda x: x.strip(" ,").split(":"), text.splitlines())
)
),
columns=["Id", "Term", "weight"]
)
print(df)
# Id Term weight
#0 4 frack 0.733
#1 4 shale 0.700
#2 4 space 0.645
#3 4 station 0.327
#4 4 nasa 0.258
#5 4 celebr 0.262
#6 4 bahar 0.345
объяснение
Я предполагаю, что вы прочитали свой файл в строку text
. Первое, что вы хотите сделать, это удалить начальные/конечные запятые и пробелы перед разделением на :
print(list(map(lambda x: x.strip(" ,").split(":"), text.splitlines())))
#[['1', ' frack 0.733, shale 0.700'],
# ['10', ' space 0.645, station 0.327, nasa 0.258'],
# ['4', ' celebr 0.262, bahar 0.345']]
Следующим шагом является разделение запятой для разделения значений и присвоение Id
каждому набору значений:
print(
[
list(map(lambda z: (y[0], *z.strip().split()), y[1].split(","))) for y in
map(lambda x: x.strip(" ,").split(":"), text.splitlines())
]
)
#[[('1', 'frack', '0.733'), ('1', 'shale', '0.700')],
# [('10', 'space', '0.645'),
# ('10', 'station', '0.327'),
# ('10', 'nasa', '0.258')],
# [('4', 'celebr', '0.262'), ('4', 'bahar', '0.345')]]
Наконец, мы используем itertools.chain.from_iterable
чтобы сгладить этот вывод, который затем можно передать прямо в конструктор DataFrame.
Примечание: распаковка кортежа *
- это функция Python 3.
Ответ 3
Предполагая, что ваши данные (csv
файл) выглядит следующим образом:
df = pd.read_csv('untitled.txt', sep=': ', header=None)
df.set_index(0, inplace=True)
# split the ','
df = df[1].str.strip().str.split(',', expand=True)
# 0 1 2 3
#-- ------------ ------------- ---------- ---
# 1 frack 0.733 shale 0.700
#10 space 0.645 station 0.327 nasa 0.258
# 4 celebr 0.262 bahar 0.345
# stack and drop empty
df = df.stack()
df = df[~df.eq('')]
# split ' '
df = df.str.strip().str.split(' ', expand=True)
# edit to give final expected output:
# rename index and columns for reset_index
df.index.names = ['Id', 'to_drop']
df.columns = ['Term', 'weight']
# final df
final_df = df.reset_index().drop('to_drop', axis=1)
Ответ 4
Просто, чтобы поместить мои два цента: вы можете написать себе парсер и передать результат в pandas
:
import pandas as pd
from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor
file = """
1: frack 0.733, shale 0.700,
10: space 0.645, station 0.327, nasa 0.258,
4: celebr 0.262, bahar 0.345
"""
grammar = Grammar(
r"""
expr = (garbage / line)+
line = id colon pair*
pair = term ws weight sep? ws?
garbage = ws+
id = ~"\d+"
colon = ws? ":" ws?
sep = ws? "," ws?
term = ~"[a-zA-Z]+"
weight = ~"\d+(?:\.\d+)?"
ws = ~"\s+"
"""
)
tree = grammar.parse(file)
class PandasVisitor(NodeVisitor):
def generic_visit(self, node, visited_children):
return visited_children or node
def visit_pair(self, node, visited_children):
term, _, weight, *_ = visited_children
return (term.text, weight.text)
def visit_line(self, node, visited_children):
id, _, pairs = visited_children
return [(id.text, *pair) for pair in pairs]
def visit_garbage(self, node, visited_children):
return None
def visit_expr(self, node, visited_children):
return [item
for lst in visited_children
for sublst in lst if sublst
for item in sublst]
pv = PandasVisitor()
out = pv.visit(tree)
df = pd.DataFrame(out, columns=["Id", "Term", "weight"])
print(df)
Это дает
Id Term weight
0 1 frack 0.733
1 1 shale 0.700
2 10 space 0.645
3 10 station 0.327
4 10 nasa 0.258
5 4 celebr 0.262
6 4 bahar 0.345
Здесь мы строим грамматику с возможной информацией: либо строка, либо пробел. line
строится из идентификатора (например, 1
), за которым следует двоеточие (:
), пропуски и pair
в term
и weight
evtl. с последующим sep
arator.
После этого нам нужен класс NodeVisitor
для фактического выполнения sth. с найденной аст.
Ответ 5
Можно просто использовать только панд:
df = pd.read_csv(StringIO(u"""1: frack 0.733, shale 0.700,
10: space 0.645, station 0.327, nasa 0.258,
4: celebr 0.262, bahar 0.345 """), sep=":", header=None)
#df:
0 1
0 1 frack 0.733, shale 0.700,
1 10 space 0.645, station 0.327, nasa 0.258,
2 4 celebr 0.262, bahar 0.345
Превратите столбец 1
в список, а затем разверните:
df[1] = df[1].str.split(",", expand=False)
dfs = []
for idx, rows in df.iterrows():
print(rows)
dfslice = pd.DataFrame({"Id": [rows[0]]*len(rows[1]), "terms": rows[1]})
dfs.append(dfslice)
newdf = pd.concat(dfs, ignore_index=True)
# this creates newdf:
Id terms
0 1 frack 0.733
1 1 shale 0.700
2 1
3 10 space 0.645
4 10 station 0.327
5 10 nasa 0.258
6 10
7 4 celebr 0.262
8 4 bahar 0.345
Теперь нам нужно str разделить последнюю строку и удалить пустые:
newdf["terms"] = newdf["terms"].str.strip()
newdf = newdf.join(newdf["terms"].str.split(" ", expand=True))
newdf.columns = ["Id", "terms", "Term", "Weights"]
newdf = newdf.drop("terms", axis=1).dropna()
В результате newdf:
Id Term Weights
0 1 frack 0.733
1 1 shale 0.700
3 10 space 0.645
4 10 station 0.327
5 10 nasa 0.258
7 4 celebr 0.262
8 4 bahar 0.345
Ответ 6
Этот код может быть сжат, но я полностью его сломал, чтобы показать логику, стоящую за ним. В принципе найти :
, определить идентификатор, разделить строку на ,
и полосу за пределами белого пространства, а затем снова разделить на пробельных символах и вызвать ваши вопросы по индексу.
Я не проверял его на крайних случаях, таких как пустые строки или пустые значения, и предполагалось, что у вас нет лишних запятых и т.д. В основном ваш файл соответствует описанию.
# need original df as df
with open('file.txt') as f:
for line in f:
id_i = line.find(':')
id = line[0:id_i] # id is everything before :
line_split = line[id_i+1:].split(',') # remaining after :
for item.strip() in line_split: # remove outer white space
item_split = item.split(' ') # break on inner white space
term = item_split[0] # reference by index
weight = item_split[1]
df = df.append({'Id': id, 'Term': term, 'weight': weight}, ignore_index=True)
Ответ 7
Вот еще один ответ на ваш вопрос. Создание списка, который будет содержать списки для каждого идентификатора и термина. И затем создайте информационный кадр.
import pandas as pd
file=r"give_your_path".replace('\\', '/')
my_list_of_lists=[]#creating an empty list which will contain lists of [Id Term Weight]
with open(file,"r+") as f:
for line in f.readlines():#looping every line
my_id=[line.split(":")[0]]#storing the Id in order to use it in every term
for term in [s.strip().split(" ") for s in line[line.find(":")+1:].split(",")[:-1]]:
my_list_of_lists.append(my_id+term)
df=pd.DataFrame.from_records(my_list_of_lists)#turning the lists to dataframe
df.columns=["Id","Term","weight"]#giving columns their names
Ответ 8
Могу ли я предположить, что перед "TERM" есть только 1 пробел?
df=pd.DataFrame(columns=['ID','Term','Weight'])
with open('C:/random/d1','r') as readObject:
for line in readObject:
line=line.rstrip('\n')
tempList1=line.split(':')
tempList2=tempList1[1]
tempList2=tempList2.rstrip(',')
tempList2=tempList2.split(',')
for item in tempList2:
e=item.split(' ')
tempRow=[tempList1[0], e[0],e[1]]
df.loc[len(df)]=tempRow
print(df)
Ответ 9
Может быть, будет легко понять, что там происходит. Вам нужно только обновить код, чтобы прочитать файл, а не использовать переменную.
import pandas as pd
txt = """1: frack 0.733, shale 0.700,
10: space 0.645, station 0.327, nasa 0.258,
4: celebr 0.262, bahar 0.345"""
data = []
for line in txt.splitlines():
key, values = line.split(':')
for elements in values.split(','):
if elements:
term, weight = elements.split()
data.append({'Id': key, 'Term': term, 'Weight': weight})
df = pd.DataFrame(data)
Ответ 10
1) Вы можете читать построчно.
2) Затем вы можете разделить ':' для вашего индекса и ',' для значений
1)
with open('path/filename.txt','r') as filename:
content = filename.readlines()
2) content = [x.split(':') для x в контенте]
Это даст вам следующий результат:
content =[
['1','frack 0.733, shale 0.700,'],
['10', 'space 0.645, station 0.327, nasa 0.258,'],
['4','celebr 0.262, bahar 0.345 ']]