Lea el archivo de pares repetidos "clave = valor" en DataFrame

11

Tengo un archivo txt con datos en este formato. Las primeras 3 líneas se repiten una y otra vez.

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

Me gustaría generar los datos en un formato de tabla, por ejemplo:

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

Estoy luchando por configurar los encabezados y simplemente recorrer los datos. Lo que he intentado hasta ahora es:

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)

La salida de eso es

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

Realmente no es lo que estoy buscando.

Flenters
fuente

Respuestas:

2

Esta solución asume que el formato de texto es el que usted ha descrito, pero puede modificarlo para usar una palabra diferente para denotar el comienzo de una nueva línea. Aquí, asumimos que una nueva línea comienza con el namecampo. Modifiqué tu myfile()función a continuación, espero que te dé algunas ideas :)

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.
martín pescador
fuente
10

Puede usar pandas para leer el archivo y procesar los datos. Puedes usar esto:

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

0     class grade name
index                 
0         B     A    1
1         A     D    2
luigigi
fuente
agregue df = pd.read_table(file, header=None), haga la siguiente línea new = df[0].str.split("=", n=1, expand=True), y esta sería mi respuesta favorita en términos de "código agradable".
MrFuppes
@ MrFuppes Edité mi respuesta. Gracias por la pista.
luigigi
1
+1 ;-) Sin embargo, acabo de ejecutar un %timeitcontra mi respuesta y me sorprendió lo lenta que es la solución de pandas puros. ¡Era aproximadamente x7 más lento en mi máquina (para un archivo txt de entrada muy pequeño)! Con la comodidad viene de arriba, con la cabeza (la mayor parte del tiempo) viene pérdida de rendimiento ...
MrFuppes
7

Sé que tienes suficientes respuestas, pero aquí hay otra forma de hacerlo usando el diccionario:

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)

Esto le da la salida como:

name grade class
0    1     A     B
1    2     D     A

Solo para tener otra perspectiva.

SSharma
fuente
3

Como tiene una salida, así es como trataría el problema:

Primero cree un índice único basado en la repetibilidad de las columnas,

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

luego usamos esto para pivotar su marco de datos usando la crosstabfunción

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
Datanovice
fuente
3

Lo que también podría hacer es leer su archivo de texto fileen bloques de 3, construir una lista anidada y ponerla en un marco de datos:

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 sería directamente

  name grade class
0    1     A     B
1    2     D     A

Nota n. ° 1: Aunque esto genera más líneas de código que una pandassolución pura , en mi experiencia es probable que sea más eficiente ya que utiliza menos pandasfunciones y, por lo tanto, menos sobrecarga.

Nota # 2: En general, argumentaría que sería mejor almacenar sus datos de entrada en otro formato, por ejemplo, jsono csv. eso facilitaría mucho la lectura, por ejemplo, con la pandasfunción read_csv en el caso de un archivo csv.

MrFuppes
fuente
0

Puede generar esa salida utilizando el módulo Diccionario de Python y 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)

Este enfoque puede no ser el más eficiente, pero no utiliza ninguna de las funciones avanzadas de Pandas. Espero eso ayude.

La salida:

    name    grade   class
0      1        A       B
1      2        D       A
Yash Ghorpade
fuente
0

En mi humilde opinión, todas las respuestas actuales parecen demasiado complicadas. Lo que haría, es usar '='como sepparámetro pd.read_csvpara leer 2 columnas, y luego pivotel DataFrame obtenido:

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

Si no desea ese índice de columna de varios niveles en el resultado, puede eliminarlo de la siguiente manera:

df.columns = df.columns.get_level_values(1)
# 0 class grade name
# 0     B     A    1
# 1     A     D    2
Georgy
fuente