Wczytaj plik powtarzających się par „klucz = wartość” do DataFrame

11

Mam plik txt z danymi w tym formacie. Pierwsze 3 linie powtarzają się w kółko.

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

Chciałbym wyprowadzić dane w formacie tabeli, na przykład:

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

Próbuję ustawić nagłówki i po prostu zapętlić dane. Do tej pory próbowałem:

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)

Wynik z tego jest

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

Nie do końca to, czego szukam.

Flentery
źródło

Odpowiedzi:

2

W tym rozwiązaniu założono, że format tekstu jest zgodny z opisem, ale można go zmodyfikować, aby używał innego słowa oznaczającego początek nowego wiersza. Zakładamy, że nowa linia zaczyna się od namepola. Zmodyfikowałem twoją myfile()funkcję poniżej, mam nadzieję, że da ci kilka pomysłów :)

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.
Kingfischer
źródło
10

Możesz użyć pand do odczytania pliku i przetworzenia danych. Możesz użyć tego:

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 Wyjścia:

0     class grade name
index                 
0         B     A    1
1         A     D    2
luigigi
źródło
dodaj df = pd.read_table(file, header=None), zrób następujący wiersz new = df[0].str.split("=", n=1, expand=True), a to byłaby moja ulubiona odpowiedź pod względem „ładnego kodu”.
MrFuppes
@MrFuppes Zredagowałem swoją odpowiedź. Dzięki za podpowiedź.
luigigi,
1
+1 ;-), jednak właśnie natrafiłem na %timeitmoją odpowiedź i dowiedziałem się, jak wolne jest rozwiązanie czystej pandy. Na moim komputerze było około x7 wolniej (dla bardzo małego wejściowego pliku txt)! Z wygody przychodzi nad głową, z
nadwyżką
7

Wiem, że masz wystarczająco dużo odpowiedzi, ale oto inny sposób zrobienia tego za pomocą słownika:

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)

To daje wynik jako:

name grade class
0    1     A     B
1    2     D     A

Aby uzyskać inną perspektywę.

SSharma
źródło
3

Ponieważ otrzymałeś wynik, w ten sposób poradziłbym sobie z problemem:

Najpierw utwórz unikalny indeks oparty na powtarzalności kolumn,

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

następnie wykorzystujemy to do przestawienia ramki danych za pomocą crosstabfunkcji

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
źródło
3

Możesz także przeczytać fileplik tekstowy w blokach po 3, zbudować zagnieżdżoną listę i umieścić ją w ramce danych:

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 byłoby bezpośrednio

  name grade class
0    1     A     B
1    2     D     A

Uwaga nr 1: Chociaż powoduje to powstanie większej liczby wierszy kodu niż czyste pandasrozwiązanie, z mojego doświadczenia wynika, że ​​może być bardziej wydajne, ponieważ korzysta z mniejszej liczby pandasfunkcji, a tym samym mniejszego obciążenia.

Uwaga 2: Ogólnie twierdzę, że lepiej byłoby przechowywać dane wejściowe w innym formacie, np . jsonLub csv. znacznie ułatwiłoby to czytanie, na przykład dzięki pandasfunkcji read_csv w przypadku pliku csv.

MrFuppes
źródło
0

Możesz wygenerować ten wynik, używając modułu słownika Python i Pand.

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)

To podejście może nie być najskuteczniejsze, ale nie wykorzystuje żadnej z zaawansowanych funkcji Pand. Mam nadzieję, że to pomoże.

Wyjście:

    name    grade   class
0      1        A       B
1      2        D       A
Yash Ghorpade
źródło
0

IMHO, wszystkie obecne odpowiedzi wyglądają na zbyt skomplikowane. Chciałbym użyć '='jako sepparametru pd.read_csvodczytu 2 kolumn, a następnie pivotuzyskanej DataFrame:

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

Jeśli nie chcesz w wyniku tego indeksu kolumn wielopoziomowych, możesz go usunąć poprzez:

df.columns = df.columns.get_level_values(1)
# 0 class grade name
# 0     B     A    1
# 1     A     D    2
Georgy
źródło