Преобразование текстового документа в специальном формате в 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 ']]