Ler arquivo de pares repetidos "chave = valor" no DataFrame


11

Eu tenho um arquivo txt com dados neste formato. As três primeiras linhas se repetem repetidamente.

name=1
grade=A
class=B
name=2
grade=D
class=A

Gostaria de exibir os dados em um formato de tabela, por exemplo:

name | grade | class
1    | A     | B
2    | D     | A

Estou lutando para definir os cabeçalhos e apenas fazer um loop sobre os dados. O que eu tentei até agora é:

def myfile(filename):
    with open(file1) as f:
        for line in f:
            yield line.strip().split('=',1)

def pprint_df(dframe):
    print(tabulate(dframe, headers="keys", tablefmt="psql", showindex=False,))

#f = pd.DataFrame(myfile('file1')
df = pd.DataFrame(myfile('file1'))
pprint_df(df)

A saída disso é

+-------+-----+
| 0     | 1   |
|-------+-----|
| name  | 1   |
| grade | A   |
| class | B   |
| name  | 2   |
| grade | D   |
| class | A   |
+-------+-----+

Não é exatamente o que estou procurando.

Respostas:


2

Esta solução supõe que o formato do texto seja o que você descreveu, mas você pode modificá-lo para usar uma palavra diferente para indicar o início de uma nova linha. Aqui, assumimos que uma nova linha começa com o namecampo. Modifiquei sua myfile()função abaixo, espero que lhe dê algumas idéias :)

def myfile(filename):
    d_list = []
    with open(filename) as f:
        d_line = {}
        for line in f:
            split_line = line.rstrip("\n").split('=')  # Strip \n characters and split field and value.
            if (split_line[0] == 'name'):
                if d_line:
                    d_list.append(d_line)  # Append if there is previous line in d_line.
                d_line = {split_line[0]: split_line[1]}  # Start a new dictionary to collect the next lines.
            else:
                d_line[split_line[0]] = split_line[1]  # Add the other 2 fields to the dictionary.
        d_list.append(d_line) # Append the last line.
    return pd.DataFrame(d_list)  # Turn the list of dictionaries into a DataFrame.

10

Você pode usar os pandas para ler o arquivo e processar os dados. Você pode usar isto:

import pandas as pd
df = pd.read_table(r'file.txt', header=None)
new = df[0].str.split("=", n=1, expand=True)
new['index'] = new.groupby(new[0])[0].cumcount()
new = new.pivot(index='index', columns=0, values=1)

new Saídas:

0     class grade name
index                 
0         B     A    1
1         A     D    2

adicione df = pd.read_table(file, header=None), faça a seguinte linha new = df[0].str.split("=", n=1, expand=True), e essa seria minha resposta favorita em termos de "código legal".
MrFuppes

@MrFuppes Eu editei minha resposta. Obrigado pela dica.
luigigi 13/11/19

11
+1 ;-) no entanto, eu apenas %timeitconcordei com a minha resposta e fiquei surpreso com a lentidão da solução dos pandas puros. Era cerca de x7 mais lento na minha máquina (para um arquivo txt de entrada muito pequeno)! Com conveniência vem em cima, com sobrecarga (na maioria das vezes) vem perda no desempenho ...
MrFuppes

7

Eu sei que você tem respostas suficientes, mas aqui está outra maneira de fazê-lo usando o dicionário:

import pandas as pd
from collections import defaultdict
d = defaultdict(list)

with open("text_file.txt") as f:
    for line in f:
        (key, val) = line.split('=')
        d[key].append(val.replace('\n', ''))

df = pd.DataFrame(d)
print(df)

Isso fornece a saída como:

name grade class
0    1     A     B
1    2     D     A

Só para ter outra perspectiva.


3

Como você tem uma saída, é assim que eu lidaria com o problema:

Primeiro, crie um índice exclusivo com base na repetibilidade das colunas,

df['idx'] = df.groupby(df['0'])['0'].cumcount() + 1
print(df)
        0  1  idx
0   name  1      1
1  grade  A      1
2  class  B      1
3   name  2      2
4  grade  D      2
5  class  A      2

então usamos isso para dinamizar seu quadro de dados usando a crosstabfunção

df1 = pd.crosstab(df['idx'],df['0'],values=df['1'],aggfunc='first').reset_index(drop=True)
print(df1[['name','grade','class']])
0 name grade class
0    1     A     B
1    2     D     A

3

O que você também pode fazer é ler seu arquivo de texto fileem blocos de 3, criar uma lista aninhada e colocá-la em um dataframe:

from itertools import zip_longest
import pandas as pd

# taken from https://docs.python.org/3.7/library/itertools.html:
def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

data = [['name', 'grade', 'class']]
with open(file, 'r') as fobj:
    blocks = grouper(fobj, 3)
    for b in blocks:
        data.append([i.split('=')[-1].strip() for i in b])

df = pd.DataFrame(data[1:], columns=data[0])  

df seria diretamente

  name grade class
0    1     A     B
1    2     D     A

Nota # 1: Embora isso crie mais linhas de código do que uma pandassolução pura , na minha experiência, é provável que seja mais eficiente, pois utiliza menos pandasfunções e menos sobrecarga.

Nota 2: Em geral, eu argumentaria que seria melhor armazenar seus dados de entrada em outro formato, por exemplo, jsonou csv. isso tornaria muito mais fácil a leitura, por exemplo, com a pandasfunção read_csv no caso de um arquivo csv.


0

Você pode gerar essa saída usando o módulo Dicionário do Python e o Pandas.

import pandas as pd
from collections import defaultdict

text = '''name=1
          grade=A
          class=B
          name=2
          grade=D
          class=A'''
text = text.split()

new_dict = defaultdict(list) 
for i in text:
    temp = i.split('=')
    new_dict[temp[0]].append(temp[1])

df = pd.DataFrame(new_dict)

Essa abordagem pode não ser a mais eficiente, mas não usa nenhuma das funções avançadas do Pandas. Espero que ajude.

A saída:

    name    grade   class
0      1        A       B
1      2        D       A

0

IMHO, todas as respostas atuais parecem muito complicadas. O que eu faria é usar '='como sepparâmetro pd.read_csvpara ler 2 colunas e, em seguida, pivoto DataFrame obtido:

import pandas as pd

df = pd.read_csv('myfile', sep='=', header=None)
#        0  1
# 0   name  1
# 1  grade  A
# 2  class  B
# 3   name  2
# 4  grade  D
# 5  class  A

df = df.pivot(index=df.index // len(df[0].unique()), columns=0)
#       1           
# 0 class grade name
# 0     B     A    1
# 1     A     D    2

Se você não quiser esse índice de coluna multinível no resultado, poderá removê-lo:

df.columns = df.columns.get_level_values(1)
# 0 class grade name
# 0     B     A    1
# 1     A     D    2
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.