Lire le fichier de paires «clé = valeur» répétées dans DataFrame


11

J'ai un fichier txt avec des données dans ce format. Les 3 premières lignes se répètent encore et encore.

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

Je voudrais sortir les données sous forme de tableau, par exemple:

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

J'ai du mal à définir les en-têtes et à parcourir les données. Ce que j'ai essayé jusqu'à présent, c'est:

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 sortie de cela est

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

Pas vraiment ce que je recherche.

Réponses:


2

Cette solution suppose que le format de texte est tel que vous l'avez décrit, mais vous pouvez le modifier pour utiliser un mot différent pour indiquer le début d'une nouvelle ligne. Ici, nous supposons qu'une nouvelle ligne commence par le namechamp. J'ai modifié votre myfile()fonction ci-dessous, j'espère que cela vous donnera quelques idées :)

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

Vous pouvez utiliser des pandas pour lire le fichier et traiter les données. Vous pouvez utiliser ceci:

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 Les sorties:

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

ajoutez df = pd.read_table(file, header=None), faites la ligne suivante new = df[0].str.split("=", n=1, expand=True), et ce serait ma réponse préférée en termes de "code sympa".
MrFuppes

@MrFuppes J'ai modifié ma réponse. Merci pour l'astuce.
luigigi

1
+1 ;-) cependant, je viens de %timeitlancer une contre ma réponse et j'ai été surpris de la lenteur de la solution des pandas purs. C'était environ x7 plus lent sur ma machine (pour un très petit fichier txt d'entrée)! Avec la commodité vient les frais généraux, avec les frais généraux (la plupart du temps) vient la perte de performances ...
MrFuppes

7

Je sais que vous avez suffisamment de réponses, mais voici une autre façon de le faire en utilisant le dictionnaire:

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)

Cela vous donne la sortie comme:

name grade class
0    1     A     B
1    2     D     A

Juste pour avoir une autre perspective.


3

Comme vous avez une sortie, voici comment je traiterais le problème:

Créez d'abord un index unique basé sur la répétabilité des colonnes,

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

nous utilisons ensuite cela pour faire pivoter votre dataframe en utilisant la crosstabfonction

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

Ce que vous pourriez également faire, c'est lire votre fichier texte filepar blocs de 3, créer une liste imbriquée et le mettre dans une trame de données:

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 serait directement

  name grade class
0    1     A     B
1    2     D     A

Note # 1: Bien que cela fasse plus de lignes de code qu'une pandassolution pure , selon mon expérience, elle est probablement plus efficace car elle utilise moins de pandasfonctions et donc moins de surcharge.

Note # 2: En général, je dirais qu'il serait préférable de stocker vos données d'entrée dans un autre format, par exemple jsonou csv. cela le rendrait beaucoup plus facile à lire, par exemple avec la pandasfonction read_csv dans le cas d'un fichier csv.


0

Vous pouvez générer cette sortie en utilisant le module Dictionnaire de Python et 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)

Cette approche n'est peut-être pas la plus efficace, mais elle n'utilise aucune des fonctions avancées de Pandas. J'espère que cela aide.

Le résultat:

    name    grade   class
0      1        A       B
1      2        D       A

0

À mon humble avis, toutes les réponses actuelles semblent trop compliquées. Ce que je ferais, c'est d'utiliser '='comme sepparamètre de pd.read_csvlire 2 colonnes, puis pivotle DataFrame obtenu:

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 vous ne voulez pas cet index de colonne à plusieurs niveaux dans le résultat, vous pouvez le supprimer en:

df.columns = df.columns.get_level_values(1)
# 0 class grade name
# 0     B     A    1
# 1     A     D    2
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.