Leggi il file delle coppie ripetute di "chiave = valore" in DataFrame


11

Ho un file txt con dati in questo formato. Le prime 3 righe si ripetono più volte.

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

Vorrei produrre i dati in un formato tabella, ad esempio:

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

Sto lottando per impostare le intestazioni e semplicemente scorrere i dati. Quello che ho provato finora è:

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)

L'output da questo è

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

Non proprio quello che sto cercando.

Risposte:


2

Questa soluzione presuppone che il formato del testo sia come descritto, ma è possibile modificarlo per utilizzare una parola diversa per indicare l'inizio di una nuova riga. Qui, supponiamo che una nuova riga inizi con il namecampo. Ho modificato la tua myfile()funzione di seguito, spero che ti dia qualche idea :)

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

Puoi usare i panda per leggere il file ed elaborare i dati. Puoi usare questo:

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 Uscite:

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

aggiungere df = pd.read_table(file, header=None), fare la seguente riga new = df[0].str.split("=", n=1, expand=True), e questa sarebbe la mia risposta preferita in termini di "bel codice".
MrFuppes,

@MrFuppes Ho modificato la mia risposta. Grazie per il suggerimento.
luigigi

1
+1 ;-) tuttavia, ho appena lanciato un %timeitcontro la mia risposta e sono stato sopraffatto dalla lentezza della soluzione di puro panda. Era circa 7 volte più lento sulla mia macchina (per un file txt di input molto piccolo)! Con comodità viene il sovraccarico, con il sovraccarico (la maggior parte del tempo) viene la perdita di prestazioni ...
MrFuppes

7

So che hai abbastanza risposte, ma ecco un altro modo di farlo usando il dizionario:

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)

Questo ti dà l'output come:

name grade class
0    1     A     B
1    2     D     A

Solo per avere un'altra prospettiva.


3

Dato che hai un output, è così che affronterei il problema:

Innanzitutto crea un indice univoco basato sulla ripetibilità delle colonne,

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

lo usiamo quindi per ruotare il tuo dataframe usando la crosstabfunzione

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

Quello che potresti anche fare è leggere il tuo file filedi testo in blocchi di 3, creare un elenco nidificato e inserirlo in un frame di dati:

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 sarebbe direttamente

  name grade class
0    1     A     B
1    2     D     A

Nota n. 1: sebbene ciò comporti più righe di codice rispetto a una pandassoluzione pura , nella mia esperienza è probabile che sia più efficiente poiché utilizza meno pandasfunzioni e quindi un sovraccarico.

Nota # 2: In generale, direi che sarebbe meglio per memorizzare i dati di input in un altro formato, ad esempio, jsono csv. ciò renderebbe molto più facile la lettura, ad esempio con la pandasfunzione read_csv nel caso di un file CSV.


0

Puoi generare quell'output usando il modulo Dizionario di Python e Panda.

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)

Questo approccio potrebbe non essere il più efficiente ma non utilizza nessuna delle funzioni avanzate di Panda. Spero che sia d'aiuto.

Il risultato:

    name    grade   class
0      1        A       B
1      2        D       A

0

IMHO, tutte le risposte attuali sembrano troppo complicate. Quello che vorrei fare è usare '='come sepparametro pd.read_csvper leggere 2 colonne e quindi pivotil DataFrame ottenuto:

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 non si desidera quell'indice di colonna a più livelli nel risultato, è possibile rimuoverlo:

df.columns = df.columns.get_level_values(1)
# 0 class grade name
# 0     B     A    1
# 1     A     D    2
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.