Czytanie danych – jedna z podstawowych czynności w data science. Każdy, kto na co dzień pracuje z danymi, wykona ją w swoim życiu zawodowym setki (o ile nie tysiące) razy. Warto zatem wiedzieć jak robić to w sposób prawidłowy.
TXT, CSV i plik płaski - kilka definicji na początek¶
Plik CSV - (ang. comma-separated values) jest formatem przechowywania danych w plikach tekstowych. Domyślnie (jak sama nazwa wskazuje) ma wartości rozdzielone przecinkiem.
Plik TXT - plik posiadający zazwyczaj rozszerzenie .txt. Jest plikiem tekstowym, przechowującym dane w postaci alfanumerycznej. Może (choć oczywiście nie musi) przechowywać dane tabelaryczne. Jest więc pewnym uogólnieniem formatu CSV.
Plik płaski - jest to po prostu stara, alternatywna nazwa dla pliku tekstowego. 🙂
Najczęstsze niespójności pomiędzy różnymi plikami płaskimi¶
Tym, co stanowi największą bolączkę podczas wczytywania plików płaskich, są niespójności w ich strukturze. Poniżej wymieniam najpopularniejsze z nich:
- Używanie różnych znaków jako separatora wartości - dosyć powszechne są średniki i tabulatory. Niekiedy można się spotkać ze znakiem "|".
- Używanie różnych separatorów dziesiętnych - w zależności od regionu może być to kropka lub przecinek.
- Używanie różnych zapisów daty - 12/12/2012, 2012-12-12, 12.12.2012, etc.
- Użyty separator dziesiętny przy wartościach całkowitoliczbowych - jeśli w danej kolumnie występują same liczby całkowite, to nie ma potrzeby, by wczytywać ją w formie 123.000.
- Data i godzina w osobnych kolumnach - to może, choć nie musi być problemem. W Pandas do przechowywania obu zazwyczaj używamy formatu "datetime", który przechowuje zarówno datę, jak i godzinę.
- Obecność/brak nagłówka/stopki - czasem np. statystyki podsumowujące zostają zapisane w ostatnim wierszu pliku.
Wiemy już, z jakiego typu problemami będziemy się mierzyć. Przejdźmy do części praktycznej i wczytajmy przykładowy zbiór.
Czytanie zbioru z pliku płaskiego - podejście #1¶
Importuję bibliotekę Pandas.
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
Użyję podstawowej metody dostępnej w Pandas - pd.read_csv
.
df = pd.read_csv('data/household_power_consumption.txt')
Sprawdźmy, czy wszystko gra...
df.head()
Widać pierwszy problem: separator kolumn użyty w zbiorze, to średnik. W Pandas separatorem domyślnie jest przecinek.
Czytanie zbioru z pliku płaskiego - podejście #2¶
Ustawiam parametr sep=';'
.
df = pd.read_csv('data/household_power_consumption.txt', sep=';')
df.head()
Wygląda nieporównywalnie lepiej, ale mamy ostrzeżenie na czerwono. Dotyczy typów zmiennych. Sprawdźmy je.
df.dtypes
Wszystko zmienne, z wyjątkiem ostatniej zostały wczytane jako "object". Dla zmiennych numerycznych nie jest to optymalny format. 😉
Próbowałem wymusić konwersję zmiennej "Global_active_power" do typu float. Nie udało się. Otrzymałem błąd:
ValueError: could not convert string to float: '?'
Brakujące bądź błędne wartości są prawdopodobnie oznaczone znakiem zapytania. Uwzględnijmy tę informację podczas czytania zbioru. 😉
By pokazać, jaki dodatkowy problem stwarza niewłaściwe wczytanie pliku, sprawdzę jego rozmiar po wczytaniu.
df.info(memory_usage='deep')
1007.3 MB - zanotujmy to jako punkt wyjściowy. 😉
Czytanie zbioru z pliku płaskiego - podejście #3¶
Ustawiam parametr na_values='?'
.
df = pd.read_csv('data/household_power_consumption.txt', sep=';', na_values='?')
df.dtypes
Teraz wygląda to znacznie lepiej. 🙂
df.info(memory_usage='deep')
Zużycie pamięci spadło z 1007.3 MB na 370. I to przez jeden dodatkowy parametr. Nieźle! 🙂 Widać jednak, że data i czas są wczytywane błędnie. Jest to problem prowadzący do potencjalnych kłopotów (np. podczas sortowania, dzielenia zbioru, walidacji krzyżowej, etc.).
Czytanie zbioru z pliku płaskiego - podejście #4¶
Podczas wczytywania pliku płaskiego, parsuję datę i godzinę dostępne w zbiorze. Używam parametrów: date_parser
i parse_dates
.
dateparser = lambda x: pd.to_datetime(x, format = '%d/%m/%Y %H:%M:%S')
df = pd.read_csv('data/household_power_consumption.txt',
sep=';',
na_values='?',
date_parser=dateparser,
parse_dates={'Date_time':['Date', 'Time']})
df.dtypes
df.info(memory_usage='deep')
To, co zwraca uwagę, to użycie we wszystkich zmiennych numerycznych typu float64
. Nie jest to optymalne. Rzućmy okiem na wartości, jakie przyjmują poszczególne zmienne.
df.agg(['min', 'median', 'max']).round(3)
Są relatywnie niewielkie. +/- 0-254. Można je spokojnie zamienić na float16.
Czytanie zbioru z pliku płaskiego - podejście #5¶
dateparser = lambda x: pd.to_datetime(x, format = '%d/%m/%Y %H:%M:%S')
import numpy as np
df = pd.read_csv('data/household_power_consumption.txt',
sep=';',
na_values='?',
dtype={
'Global_active_power':np.float16,
'Global_reactive_power':np.float16,
'Voltage':np.float16,
'Global_intensity':np.float16,
'Sub_metering_1':np.float16,
'Sub_metering_2':np.float16,
'Sub_metering_3':np.float16
},
date_parser=dateparser,
parse_dates={'Date_time':['Date', 'Time']})
df.info(memory_usage='deep')
Zbiór teraz zajmuje w pamięci jedynie 43.5 MB. Oznacza to, że poprzez wykonanie powyższych operacji udało się obniżyć użycie pamięci o ponad 95%! 🙂
Podsumowanie¶
Jeśli masz jakieś pytania, to proszę, podziel się nimi w komentarzu pod wpisem - zapraszam do dyskusji. Jeśli artykuł przypadł Ci do gustu, to proszę, podziel się nim w mediach społecznościowych ze swoimi znajomymi. Będę bardzo wdzięczny. 🙂
PODOBAŁ CI SIĘ TEN ARTYKUŁ?
Jeśli tak, to zarejestruj się, by otrzymywać informacje o nowych wpisach.
Dodatkowo w prezencie wyślę Ci bezpłatny poradnik :-)
Kolumny dot. sub_metering na pierwszy rzut oka wyglądają na integery. Czy pandas ogarnąłby, że tam są integery, a daje float bo gdzieś jakaś komórka miała flot’a
Cześć Bart! Dzięki za komentarz i brawo za spostrzegawczość. 🙂
Tak, to są integery. Pandas normalnie powinien to ogarnąć, ale w tym przypadku zmienne „Sub_metering” zawierają brakujące wartości, dlatego muszą być przechowywane jako float. Jeżeli będziemy chcieli wymusić konwersję zmiennej do liczby całkowitej, to Pandas zwróci błąd:
> „Cannot convert non-finite values (NA or inf) to integer”