W tej części zajmiemy się oszczędnością czasu i procesora, spróbujemy zapisać Dataframe jako plik, by nie musieć za każdym razem sięgać do treści książek.
Pewnie zauważyłeś, że przy każdym uruchomieniu naszego programu otwierany jest plik .txt, usuwane przypisy, usuwane znaki nie będące literami, tworzone są listy trzech wyrazów, zliczane i umieszczane w Dataframe. Im więcej książek, ta operacja będzie trwać dłużej i dłużej. Najwyższy czas pożegnać się z ciągłym wracaniem do początku i zapisać Dataframe do pliku, a następnie korzystać z zapisanego, gotowego Dataframe. Pewnie zdarzyło ci się spotkać z plikami .csv (comma-separated values), to po prostu plik tekstowy w którym dane oddzielane są przecinkami (comma). Nic nie stoi na przeszkodzie by uzyskane przez nas dane zapisać w tej postaci. Wykorzystamy do tego funkcję to_csv().Jako argument funkcji podajemy nazwę pliku wraz ze ścieżką zapisu. Popatrz na nasz program:
from os import listdir
import re
from collections import Counter
import pandas as pd
sciezka = "F:\\python\\ids\\"
sciezka_csv = "F:\\python\\csv\\"
pliki = listdir(sciezka)
#dla każdego pliu w katalogu
for plik in pliki:
#otwórz plik
ksiazka = open(sciezka + plik, mode="r", encoding = "utf-8")
tekst = ksiazka.read()
#usuń kłopotliwe przypisek T. D. M. i przypisy
if plik == "Pamietnik pani Hanki - Tadeusz Dolega-Mostowicz.txt":
tekst = tekst.replace("(Przypisek T. D. M.)","")
elif plik == "Dziecko salonu - Janusz Korczak.txt" or \
plik == "Kiedy znow bede maly - Janusz Korczak.txt" or \
plik == "Bankructwo malego Dzeka - Janusz Korczak.txt":
tekst = tekst.split("Przypisy:")[0]
#przygotuj tekst do zliczania powtórzeń słów
tekst = tekst.replace("\n", " ")
tekst = tekst.lower()
tekst = re.sub("[^a-z ąćęłńóśóżź]", "", tekst)
tekst = re.sub(" +", " ", tekst)
wyrazy = tekst.split()
lista_trzech_wyrazow = []
#utwórz listę trzech kolejnych wyrazów
for indeks in range(len(wyrazy) - 2):
trzy_wyrazy = " ".join(wyrazy[indeks:indeks + 3])
lista_trzech_wyrazow.append(trzy_wyrazy)
#utwórz słownik zliczający liczbę powtórzeń trzech kolejnych wyrazów
liczba_trzygramow = Counter(lista_trzech_wyrazow)
#nadaj tytuł kolumnie zliaczania równy inicjałom i tytułowi książki
if plik.find("Tadeusz Dolega-Mostowicz") > -1:
autortytul = (f"TDM {plik.split(' - Tadeusz Dolega-Mostowicz')[0]}")
else:
autortytul = (f"JK {plik.split(' - Janusz Korczak')[0]}")
#utwórz Dataframe (df) ze słownika
df = pd.DataFrame(liczba_trzygramow.items(),columns = ["Trzygram",autortytul])
#posortuj od najliczniej występującego trzygramu
df = df.sort_values(by = autortytul, ascending = False)
#wyeksoprtuj dataframe do pliku .csv
df.to_csv(f"{sciezka_csv}{autortytul}.csv")
W 7 linijce zdefiniowaliśmy ścieżkę zapisu (najpierw musisz utworzyć katalog o takiej nazwie, nie rób tego jako podkatalog katalogu w którym są pliki książek, to komplikuje odczytanie plików .txt). W 45 linijce wykorzystujemy funkcję na naszym Dataframe (df) z argumentem podającym ścieżkę, a nazwa piliku jest taka sama jak nazwa kolumny z liczbą powtórzeń 3-gramu z części 14. Efekt uruchomionego programu tym razem będzie tylko widoczny w wybranym katalogu, u mnie wygląda to tak:

Otworzę pierwszy plik za pomocą notatnika (nie excela jak sugeruje ikonka), kilka górnych wierszy wygląda tak:
,Trzygram,JK Bankructwo malego Dzeka
3415,pani powiedziała że,11
6376,że się nie,8
6761,żeby się nie,8
33596,bo nie ma,7
(...)
Tak wygląda plik .csv od środka. Pierwsza linijka to tytuły kolumn. Pierwsza kolumna zawiera numerację nadaną automatycznie przez Dataframe i ta kolumna nie ma nazwy, następne mamy nazwy naszych dwóch kolumn.
Teraz zamiast odczytywać i „obrabiać” za każdym razem pliki z książkami odczytajmy i wydrukujmy nasze .csv. Do otwarcia pliku .csv użyjemy funkcji read_csv(), której argumentem jest nazwa pliku wraz ze ścieżką. Program będzie wyglądał tak:
from os import listdir
import pandas as pd
sciezka = "F:\\python\\csv\\"
pliki = listdir(sciezka)
#dla każdego pliu w katalogu
for plik in pliki:
#wydrukuj pierwsze dwa wiersze
df = pd.read_csv(f"{sciezka}{plik}")
print(df.head(2))
Prawda, że nasz program zrobił się dużo krótszy? W 4 linijce zmieniamy ścieżkę na taką, gdzie zapisałeś pliki .csv (i tylko pliki .csv, nic innego nie może się znaleźć w tym katalogu). W linijce 10 funkcją read_csv() odczytujemy pliki i w 11 linijce drukujemy pierwsze dwie linijki każdego z plików. Zobaczmy efekt:
====================== RESTART: F:/python38/wpis 15 csv.py =====================
Unnamed: 0 Trzygram JK Bankructwo malego Dzeka
0 3415 pani powiedziała że 11
1 6376 że się nie 8
Unnamed: 0 Trzygram JK Dzieci ulicy
0 1696 w tej chwili 13
1 14253 ja wiem że 8
Unnamed: 0 Trzygram JK Dziecko salonu
0 3107 co to jest 17
1 2879 ja nie chcę 13
Unnamed: 0 Trzygram JK Kajtus Czarodziej
0 295 nie wie co 9
1 1607 co to za 9
Unnamed: 0 Trzygram JK Kiedy znow bede maly
0 606 nie wiem czy 18
1 3759 nie wiem co 13
Unnamed: 0 Trzygram JK Krol Macius na wyspie bezludnej
0 483 wasza królewska mość 27
1 44457 dziś popr forma 22
Unnamed: 0 Trzygram TDM Bracia Dalcz i S-ka
0 1479 w każdym razie 55
1 5833 w ten sposób 40
Unnamed: 0 Trzygram TDM Dr. Murek zredukowany
0 2719 w każdym razie 30
1 711 w związku z 12
Unnamed: 0 Trzygram TDM Drugie zycie doktora Murka
0 5274 w każdym razie 20
1 18308 okazało się że 12
Unnamed: 0 Trzygram TDM Kariera Nikodema Dyzmy
0 8614 kochany panie nikodemie 23
1 3510 che che che 18
Unnamed: 0 Trzygram TDM Kiwony
0 2206 do przekonania że 13
1 2783 w każdym razie 11
Unnamed: 0 Trzygram TDM Pamietnik pani Hanki
0 2257 w każdym razie 40
1 2899 o tym że 28
>>>
Dataframe wygląda nieco inaczej niż ten sam przed zapisem. Indeksy wierszy stały się dodatkową kolumną (Unnamed: 0), a Python dodał nowe indeksy, to zupełnie nam nie przeszkadza. Jeśli chcesz być trochę mniej inferior a bardziej superior, możesz jako dodatkowy argument funkcji read_csv() określić, która kolumna pliku .csv jest indeksem. W naszym przypadku do Dataframe został dodany indeks i zapisany w pliku .csv jako pierwsza kolumna. Znamy już na tyle programowanie i wiemy, że numeracja kolumn zaczyna się od 0. Dodajmy argument wskazujący która kolumna jest indeksem:
from os import listdir
import pandas as pd
sciezka = "F:\\python\\csv\\"
pliki = listdir(sciezka)
#dla każdego pliu w katalogu
for plik in pliki:
#wydrukuj pierwsze dwa pierwsze
df = pd.read_csv(f"{sciezka}{plik}", index_col = [0])
print(df.head(2))
Zauważ, że teraz funkcja read_csv() ma dwa argumenty read_csv(sciezka + plik, index_col = [0]), czyli prócz ścieżki podajemy, że kolumną z indeksem (index_col) jest 1 kolumna ( = [0]). Zobaczmy efekt:
====================== RESTART: F:/python38/wpis 15 csv.py =====================
Trzygram JK Bankructwo malego Dzeka
3415 pani powiedziała że 11
6376 że się nie 8
Trzygram JK Dzieci ulicy
1696 w tej chwili 13
14253 ja wiem że 8
Trzygram JK Dziecko salonu
3107 co to jest 17
2879 ja nie chcę 13
Trzygram JK Kajtus Czarodziej
295 nie wie co 9
1607 co to za 9
Trzygram JK Kiedy znow bede maly
606 nie wiem czy 18
3759 nie wiem co 13
Trzygram JK Krol Macius na wyspie bezludnej
483 wasza królewska mość 27
44457 dziś popr forma 22
Trzygram TDM Bracia Dalcz i S-ka
1479 w każdym razie 55
5833 w ten sposób 40
Trzygram TDM Dr. Murek zredukowany
2719 w każdym razie 30
711 w związku z 12
Trzygram TDM Drugie zycie doktora Murka
5274 w każdym razie 20
18308 okazało się że 12
Trzygram TDM Kariera Nikodema Dyzmy
8614 kochany panie nikodemie 23
3510 che che che 18
Trzygram TDM Kiwony
2206 do przekonania że 13
2783 w każdym razie 11
Trzygram TDM Pamietnik pani Hanki
2257 w każdym razie 40
2899 o tym że 28
>>>
Świetnie, wszystko działa. Pandas oferuje nie tylko eksport do .csv, możemy zapisać pliki w szeregach formatów, np. .xlsx, .html, .json. My skupimy się na formacie pliku pickle. Format pickle zapisuje Dataframe jako strumień bajtów (robi serializację). Zapis, odczyt, wprowadzanie zmian w plikach typu pickle są szybsze niż w .csv. O ile szybsze? Zaraz sprawdzimy, ale zacznijmy od zapisania Dataframe do plików pickle:
from os import listdir
import re
from collections import Counter
import pandas as pd
sciezka = "F:\\python\\ids\\"
sciezka_pickle = "F:\\python\\pickle\\"
pliki = listdir(sciezka)
#dla każdego pliu w katalogu
for plik in pliki:
#otwórz plik
ksiazka = open(sciezka + plik, mode="r", encoding = "utf-8")
tekst = ksiazka.read()
#usuń kłopotliwe przypisek T. D. M. i przypisy
if plik == "Pamietnik pani Hanki - Tadeusz Dolega-Mostowicz.txt":
tekst = tekst.replace("(Przypisek T. D. M.)","")
elif plik == "Dziecko salonu - Janusz Korczak.txt" or \
plik == "Kiedy znow bede maly - Janusz Korczak.txt" or \
plik == "Bankructwo malego Dzeka - Janusz Korczak.txt":
tekst = tekst.split("Przypisy:")[0]
#przygotuj tekst do zliczania powtórzeń słów
tekst = tekst.replace("\n", " ")
tekst = tekst.lower()
tekst = re.sub("[^a-z ąćęłńóśóżź]", "", tekst)
tekst = re.sub(" +", " ", tekst)
wyrazy = tekst.split()
lista_trzech_wyrazow = []
#utwórz listę trzech kolejnych wyrazów
for indeks in range(len(wyrazy) - 2):
trzy_wyrazy = " ".join(wyrazy[indeks:indeks + 3])
lista_trzech_wyrazow.append(trzy_wyrazy)
#utwórz słownik zliczający liczbę powtórzeń trzech kolejnych wyrazów
liczba_trzygramow = Counter(lista_trzech_wyrazow)
#nadaj tytuł kolumnie zliaczania równy inicjałom i tytułowi książki
if plik.find("Tadeusz Dolega-Mostowicz") > -1:
autortytul = (f"TDM {plik.split(' - Tadeusz Dolega-Mostowicz')[0]}")
else:
autortytul = (f"JK {plik.split(' - Janusz Korczak')[0]}")
#utwórz Dataframe (df) ze słownika
df = pd.DataFrame(liczba_trzygramow.items(),columns = ["Trzygram",autortytul])
#posortuj od najliczniej występującego trzygramu
df = df.sort_values(by = autortytul, ascending = False)
#wyeksoprtuj dataframe
df.to_pickle(f"{sciezka_pickle}{autortytul}.pkl")
W 7 linijce zmieniliśmy ścieżkę na prowadzącą do katalogu, gdzie zapiszemy nasze pliki pickle (pamiętaj, najpierw utwórz ten katalog). W 44 linijce używamy funkcji to_pickle() w podobny sposób, jak funkcji to_csv() zmieniając jedynie rozszerzenie pliku na .pkl. Sprawdźmy, czy pliki się zapisały:

Podejrzymy zawarość takiego pliku w notatniku:
€•ż Śpandas.core.frame”Ś DataFrame”“”)”}”(Ś_data”Śpandas.core.internals.managers”ŚBlockManager”“”)”(]”(Śpandas.core.indexes.base”Ś
_new_Index”“”hŚIndex”“”}”(Śdata”Śnumpy.core.multiarray”Ś_reconstruct”“”Śnumpy”Śndarray”“”K …”Cb”‡”R”(KK…”hŚdtype”“”ŚO4”K K‡”R”(KŚ|”NNNJ˙˙˙˙J˙˙˙˙K?t”b‰]”(ŚTrzygram”ŚJK Kiedy znow bede maly”et”bŚname”Nu†”R”h
Śpandas.core.indexes.numeric”Ś
Int64Index”“”}”(hŚnumpy.core.numeric”Ś_frombuffer”“”(– ´ ^ Ż ś ń ¸ ,
(...)
KM„–†”h!‰]”(Śnie wiem czy”Śnie wiem co”ŚsiÄ™ zdaje ĹĽe”Śsam nie wiem”Ś a ja siÄ™”Śkiedy byĹ‚em dorosĹ‚y”Ś a on siÄ™”ŚĹĽeby siÄ™ nie”Ś i nic nie”Św tej chwili”Śa ja nie”Śtak mi siÄ™”ŚĹĽe siÄ™ nie”Śkiedy byĹ‚em nauczycielem”Śmi siÄ™ ĹĽe”Ś
i tak siÄ™”Świem co robi攌
siÄ™ z nim”Ś
(...)
Dużo mniej przyjemny do zrozumienia, prawda? Ale nam wystarczy jeśli zrozumie go Python!
Tak samo jak poprzednio zaimportujmy pliki .pkl do Dataframe i wydrukujemy. Użyjemy do tego funkcji read_pickle(). Argumentem, jak poprzednio, będzie ścieżka wskazująca każdy z plików:
from os import listdir
import pandas as pd
sciezka = "F:\\python\\pickle\\"
pliki = listdir(sciezka)
#dla każdego pliu w katalogu
for plik in pliki:
#wydrukuj pierwsze dwa pierwsze
df = pd.read_pickle(f"{sciezka}{plik}")
print(df.head(2))
Zobaczmy efekt:
====================== RESTART: F:/python38/wpis 15 pkl.py =====================
Trzygram JK Bankructwo malego Dzeka
3415 pani powiedziała że 11
6376 że się nie 8
Trzygram JK Dzieci ulicy
1696 w tej chwili 13
14253 ja wiem że 8
Trzygram JK Dziecko salonu
3107 co to jest 17
2879 ja nie chcę 13
Trzygram JK Kajtus Czarodziej
295 nie wie co 9
1607 co to za 9
Trzygram JK Kiedy znow bede maly
606 nie wiem czy 18
3759 nie wiem co 13
Trzygram JK Krol Macius na wyspie bezludnej
483 wasza królewska mość 27
44457 dziś popr forma 22
Trzygram TDM Bracia Dalcz i S-ka
1479 w każdym razie 55
5833 w ten sposób 40
Trzygram TDM Dr. Murek zredukowany
2719 w każdym razie 30
711 w związku z 12
Trzygram TDM Drugie zycie doktora Murka
5274 w każdym razie 20
18308 okazało się że 12
Trzygram TDM Kariera Nikodema Dyzmy
8614 kochany panie nikodemie 23
3510 che che che 18
Trzygram TDM Kiwony
2206 do przekonania że 13
2783 w każdym razie 11
Trzygram TDM Pamietnik pani Hanki
2257 w każdym razie 40
2899 o tym że 28
>>>
Już tutaj widzisz przydatną właściwość pickle, pickle pamięta co było indeksem, więc ty nie musisz pamiętać o dodatkowym argumencie, jak to było w przypadku read_csv(). Druga rzecz to szybkość, zmierzyłem w 100 powtórzeniach, jak szybko Python odczyta i wydrukuje pierwsze 10 linijek z naszych 12 plików w formacie .csv i .pkl. Średnia wyciągnięta ze 100 pomiarów:
Odczyt 12 .csv: 0.07213027899999998
Odczyt 12 .pkl: 0.07171955499999991
Wynik jest trochę dyskusyjny, mówimy tu o różnicach mniejszych niż tysięczne sekundy. Kolejna rzecz, jaką można porównać, to rozmiar plików. W przypadku .csv 12 naszych plików ma rozmiar 23 928 169 bajtów, w przypadku .pkl jest to 31 400 811 bajtów. Jeśli chcesz dowiedzieć się więcej na temat formatów, szybkości odczytu, zapisu, rozmiaru plików zajrzyj tutaj. A tymczasem to wszystko na dziś, w następnej części zajmiemy się wizualizacją danych, czyli wykresami!
Zadanie domowe
Podążanie za instrukcjami, nawet jeśli je wszystkie wykonujesz samodzielnie, nie zrobi z ciebie programisty. Zadania domowe mogą wydawać się na początku trudne. Tu nie ma rozwiązania podanego na talerzu, użyj dowolnych źródeł, by znaleźć odpowiedź.
- W tym rozdziale pisałem o komplikacjach, jakie niesie ze sobą tworzenie podfolderu w folderze z plikami książek w formacie .txt. W zadaniu domowym utwórz w folderze z książkami .txt dwa foldery nazwane csv i pkl, zmień program tak, by poprawnie odczytał książki i zapisał je do folderów odpowiadających ich formatowi.
- Wyeksportuj pliki do formatu .json i .html. Podejrzyj oba w notatniku, otwórz plik .html w przeglądarce internetowej, jak oceniasz prędkość eksportu do .html?