Python: Jaki byłby najbardziej efektywny sposób na odczytanie pliku bez domyślnego separatora z milionami rekordów i umieszczenie go w „ramce danych (pandach)”?

Plik to: "file_sd.txt"

 A123456MESTUDIANTE 000-12
 A123457MPROFESOR   003103
 I128734MPROGRAMADOR00-111
 A129863FARQUITECTO 00-456
# Fields and position:
# - Activity Indicator :  indAct     -> 01 Character
# - Person Code        :  codPer     -> 06 Characters
# - Gender (M / F)     :  sex        -> 01 Character
# - Occupation         :  occupation -> 11 Characters
# - Amount(User format):  amount     -> 06 Characters (Convert to Number)

Nie jestem pewny. Czy to najlepsza opcja ?:

 import pandas as pd 
 import numpy as np

 def stoI(cad):
     pos =  cad.find("-")
     if pos < 0: return int(cad)  
     return int(cad[pos+1:])*-1 

 #Read Txt
 data = pd.read_csv(r'D:\file_sd.txt',header = None)
 data_sep = pd.DataFrame(
     {
         'indAct'   :data[0].str.slice(0,1),
         'codPer'   :data[0].str.slice(1,7),
         'sexo'     :data[0].str.slice(7,8),
         'ocupac'   :data[0].str.slice(8,19),
         'monto'    :np.vectorize(stoI)(data[0].str.slice(19,25))
     })
 print(data_sep)

   indAct  codPer sexo       ocupac  monto
 0      A  123456    M  ESTUDIANTE     -12
 1      A  123457    M  PROFESOR      3103
 2      I  128734    M  PROGRAMADOR   -111
 3      A  129863    F  ARQUITECTO    -456

** To rozwiązanie dla 7 milionów wierszy. Wynik: **

%timeit df_slice()
11.1 s ± 166 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
3
Victor Cervantes 18 grudzień 2019, 03:07

2 odpowiedzi

Masz plik o stałej szerokości, więc powinieneś użyć odpowiedniego Czytelnik pd.read_fwf. W tym przypadku określimy liczbę znaków, które należą do każdego pola oraz nazwy kolumn.

df = pd.read_fwf('test.txt', header=None, widths=[1, 6, 1, 11, 6])
df.columns = ['indAct' ,'codPer', 'sexo', 'ocupac', 'monto']
#  indAct  codPer sexo       ocupac   monto
#0      A  123456    M   ESTUDIANTE  000-12
#1      A  123457    M     PROFESOR  003103
#2      I  128734    M  PROGRAMADOR  00-111
#3      A  129863    F   ARQUITECTO  00-456

Teraz możesz ustalić dtypy pól. 'monto' można przekształcić w liczbę przez usunięcie zer i wywołanie pd.to_numeric.

df['monto'] = pd.to_numeric(df['monto'].str.strip('0'), errors='coerce')
#  indAct  codPer sexo       ocupac  monto
#0      A  123456    M   ESTUDIANTE    -12
#1      A  123457    M     PROFESOR   3103
#2      I  128734    M  PROGRAMADOR   -111
#3      A  129863    F   ARQUITECTO   -456

Jak zauważyłeś w komentarzu, może to pozornie wydawać się wolniejsze. Zaletą jest to, że pd.read_fwf, ponieważ operacja we / wy ma dużo automatycznego czyszczenia danych.

  • Prawidłowo wyrzuci kolumny z obiektu, jeśli wszystkie dane są int / float / numeric. W przypadku krojenia ciągów należy ręcznie wpisać kolumny.
  • Prawidłowo usunie białe znaki z ciągów w polach, które nie wykorzystują w pełni limitu znaków. Jest to dodatkowy krok, który należy wykonać po krojeniu.
  • Prawidłowo wypełnia brakujące dane (wszystkie puste pola) NaN. Cięcie strun zachowuje puste napisy i musi być traktowane oddzielnie. pandas nie rozpoznaje '' jako zerowe, więc tak powinno być właściwie obsługiwane brakujące dane.

W przypadku wielu kolumn obiektów, które w pełni obejmują cały limit znaków, bez brakujących danych, krojenie na ciągi ma tę zaletę. Ale w przypadku ogólnie nieznanego zbioru danych, który musisz pozyskać i ETL, po rozpoczęciu usuwania ciągów znaków i konwersji typów w każdej kolumnie, prawdopodobnie okaże się, że wyznaczone operacje we / wy pandas są najlepszą opcją.

1
ALollz 21 grudzień 2019, 01:00
To rozwiązanie jest wolniejsze, gdy jest więcej wierszy (miliony wierszy). Na przykład 7 milionów wierszy. wynik to: %timeit df_slice() 10.2 s ± 498 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) def df_fwf(): df = pd.read_fwf(r'D:\file_sin_delimitadores.txt', header=None, widths=[1, 6, 1, 11, 6]) df.columns = ['indAct' ,'codPer', 'sexo', 'ocupac', 'monto'] df['monto'] = pd.to_numeric(df['monto'].str.strip('0'), errors='coerce') return df %timeit df_fwf() 35 s ± 2.9 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
 – 
Victor Cervantes
20 grudzień 2019, 23:48
Zobacz aktualizację. pd.read_fwf wykonuje wiele innych operacji czyszczenia danych i konwersji typów, ponieważ jest to operacja we/wy. Jeśli planujesz przeprowadzać jakiekolwiek analizy, prawdopodobnie te operacje i tak będą wymagane wcześniej (konwersja na numeryczne, usuwanie białych znaków z ciągów, reprezentowanie brakujących danych jako NaN). Tak więc, chociaż być może udało Ci się uzyskać nieco szybszy sposób dzielenia danych na kolumny, w większości przypadków większość czyszczenia danych została przeniesiona do innego kroku.
 – 
ALollz
21 grudzień 2019, 01:02

Możesz wyodrębnić kolumny używające dopasowywania wzorców wyrażeń regularnych. Dla danych, o których mowa, możemy zdefiniować wyrażenie regularne jako:

data[0].str.extract('(?P<indAct>[AI]{1})(?P<codPer>[0-9]{6})(?P<sexo>[MF]{1})(?P<ocupac>[A-Z\s]{11})[0]*[^-|1-9](?P<monto>[0-9-\s]*$)')

Założenie jest takie, że dane są czyste, co może, ale nie musi, być prawidłowym założeniem.

Oto porównanie podejścia w pytaniu i tutaj:

#Data size is 300 rows. ( 4 rows in the question replicated 75 times)



import pandas as pd 
import numpy as np

 #Returns a number from a string with zeros to the left, For example: stoI('0000-810') return -810
def stoI(cad):
    pos =  cad.find("-")
    if pos < 0: return int(cad)  
    return int(cad[pos+1:])*-1 

data = pd.read_csv('file_sd.txt',header = None)

#Read Txt
def df_slice(): 
    return pd.DataFrame(
     {
         'indAct'   :data[0].str.slice(0,1),
         'codPer'   :data[0].str.slice(1,7),
         'sexo'     :data[0].str.slice(7,8),
         'ocupac'   :data[0].str.slice(8,19),
         'monto'    :np.vectorize(stoI)(data[0].str.slice(19,25))
     })

def df_extract():
    return data[0].str.extract('(?P<indAct>[AI]{1})(?P<codPer>[0-9]{6})(?P<sexo>[MF]{1})(?P<ocupac>[A-Z\s]{11})[0]*[^-|1-9](?P<monto>[0-9-\s]*$)')


%timeit df_slice()
1.84 ms ± 30.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


%timeit df_extract()
975 µs ± 15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Mam nadzieję że to pomoże!

0
S.Au.Ra.B.H 18 grudzień 2019, 05:19
To rozwiązanie jest wolniejsze, gdy jest więcej wierszy (miliony wierszy). Na przykład 7 milionów wierszy. wynik to: %timeit df_slice() 10.2 s ± 498 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) %timeit df_extract() 12.5 s ± 1.07 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
 – 
Victor Cervantes
20 grudzień 2019, 23:32
Świetnie ! Myślę, że metoda, z której korzystasz, jest najbardziej efektywna dla Twoich danych.
 – 
S.Au.Ra.B.H
21 grudzień 2019, 01:03