Александър Иванов
Не, тук не става дума за бамбукови мечета. Името на този питонски модул е съкращение от PANel DAta, а -s е прибавено било за да се подчертае голямото количество данни, които могат да се обработват, било за да се направи езиковата игра, било заради двете.
След 2000-та година електронната техника започна да поевтинява още по-устремно — поевтиня единицата памет (операционна или дискова), поевтиняха процесорите. Никого вече не можеш да изненадаш с 4-, 6- или 8-ядрен процесор. А това направи достъпни компютри с много операционна памет — 32, 64, че и 128 GB, с мощни процесори и с бърза мрежова връзка. Започнаха да наричат тези компютри работни станции, а и да ги свързват в групи (кластъри) в мрежа, за да решават задачи, които доскоро бяха по силите само на суперкомпютрите.
Появи се и съвременно направление в обработката на данни, което нарекоха Big data. Основната идея тук е огромно количество данни — често това са таблици с милиони редове и с десетки или стотици колони — да се заредят и да се обработват в бързата операционна памет, като по възможност се използват успоредно всички процесори. Резултатите от тази интензификация в обработката на данни личи и в ежедневието ни, но все пак най-полезна е тя за учения изследовател.
Подходът „големи данни“ има, разбира се, и своите критици. Основното възражение е, че като се гледа гората, не се виждат дърветата. Така може напълно да бъдат пропуснати интересни и важни процеси.
В известен смисъл методиката на „големите данни“ е противоположна на „микроскопската“ методика, която Янакиев развива в глотометрията. Но глотометристът, като е наясно с това, може да използва и двата подхода. Те се допълват, а не си противоречат.
За да ви създам някаква представа за подхода „големи данни“, аз ще използвам питонския модул pandas, един от най-достъпните инструменти в тази област, и данните за имена на български граждани. Само имайте предвид две неща.
Първо, нашата извадка с имена на български граждани от избирателните списъци трудно може да бъде определена като „големи данни“. Със своите по-малко от седем милиона реда и с девет колони таблицата ни заема по-малко от 1 GB на диска.
Второ: и все пак, за да следите нещата, които показвам, ще са ви необходими поне два GB операционна памет, по-добре четири.
Първо, трябва да изтеглите архива all_text.csv.zip, ако нямате вече при себе си файла all_text.csv (вж. CSV — Comma-Separated Values). Изтеглянето може да стане и от конзолата с програмата curl и с unzip, например така:
Предполагам, че вече сте инсталирали Python, при това Python 3.x.
Ако още не сте, най-лесният начин да стане това е чрез Miniconda: инсталира се питонът, още няколко необходими и полезни пакета, но най-вече — пакетният менажър conda.
Проверете дали някой от инсталираните пакети не трябва да се обнови. Това става в конзолата/терминала със следната команда:
$ conda upgrade --all
След това трябва да инсталирате още няколко програмни пакета:
$ conda install numpy scipy matplotlib hdf5 pytables pandas ipython qtconsole notebook
Пакетният менажър ще „пресметне“ зависимостите и ще инсталира всичко, което е необходимо. За всеки случай проверете отново дали няма някой пакет за обновяване:
$ conda upgrade --all
и след това малко разчистете:
$ conda clean --all
Това изчиства пакетните архивни файлове, старите версии на софтуера и освобождава доста място на диска.
Тези две форми на командата (с upgrade и с clean) използвайте по-честичко — програмните пакети в хранилищата на Anaconda се обновяват активно.
Оттук нататък нещата, които показвам, са изпълними както в jupyter qtconsole, така и в jupyter notebook. Може, разбира се, да работите и в терминала с ipython, но ще трябва да определите къде ще се създават графиките.
Моят опит сочи, че за оперативна работа с данните jupyter qtconsole е по-удобна програма, но ако искате да пишете красиви учебници, като този, в които да редувате текст с изпълними програми, jupyter notebook е по-подходяща програма, а jupyter-lab може би е и по-удобна.
И е време
Първо, да заредим необходимите за работа модули:
import numpy as np
import pandas as pd
Необходимо е да приготвим имена за деветте колони в таблицата — по-късно ще ги използваме, за да определяме с коя колона работим:
nm = ['lichno', 'bashtino', 'familno', 'pol', 'rayon', 'obshtina', 'kmetstvo', 'n_myasto', 'sekciya']
И да създадем таблицата:
data = pd.read_csv('all_text.csv', sep='|', names=nm, dtype='str')
data
Методът .read_csv() на модула pandas изчита файла във формат .CSV и трябва да определим името му. По подразбиране разделител (separator) между полетата в реда е запетайката, затова тук с именувания параметър sep аз трабва да го сменя. С именувания параметър names трябва да определя и имената на колоните; иначе pandas приема първия ред за имена на колоните, а при нашите данни това е безсмислено. Вместо с параметъра names=nm опитайте да изчетете данните с параметъра header=None — това е инструкция да не се използва първият ред от таблицата за имена на колоните и pandas просто ще номерира колоните от 0 нататък. Имената на колоните могат да се сменят и по-късно. Последният именуван параметър dtype определя типа на данните в цялата таблица и ми спестява едно съобщение за грешка, което ще се получи без него заради празните полета. Иначе и без него данните ще се изчетат по същия начин, но със съобщение за грешка.
Да опитаме.
data = pd.read_csv('all_text.csv', sep='|', header=None)
data.head(5)
Грешката ни посочва, че проблемът е в шестата колона: това е колоната „кметство“, която в огромния брой случаи е празна. Като създава таблицата, модулът pandas по подразбиране запълва празните полета с NaN. Но какво значи NaN?
Съкращение от Not a Number (не е число). Това е специален тип за дании, въведен в модула numpy (numeric python, специално създаден модул за работа с количествени данни), който модул се използва и в pandas.
Програмното осигуряване е култура и новосъздаваните неща стъпват, използват, наследяват създадените преди това. Така pandas наследява и специално създадения тип NaN, отбелязващ липса на данни.
Типът NaN е твърде интересен и особен. Вижте:
type(np.nan)
Значи, число с „плаваща запетая“, число с цяла и дробна част. Само че с него не могат да се правят нито аритметични действия, нито дори сравнения:
np.nan == np.nan
И, в общи линии, не може да се използва за нищо, в таблицата по него дори търсене не можем да направим, ако не използваме специалните методи .isna() и .notna(). NA е общо наименование за липсващи данни, независимо от типа им.
Затова аз сега ще го заменя с празен стринг.
Но нека първо оправим имената на колоните:
data.columns = nm
data.tail(5)
И сега:
data = data.fillna('')
data[:4]
Цялата тази хубост можеше да се постигне още при изчитането на данните, ако бях „предупредил“ pandas да не запълва липсващите данни с NaN:
data = pd.read_csv('all_text.csv', sep='|', names=nm, keep_default_na=False)
data[-5:]
Само че така нямаше да мога да ви обърна внимание върху загадъчния тип NaN.
Обърнахте ли внимание, че към таблицата можем да се отнасяме като към всеки друг списък (list) в питона? Можем да си режем от нея филийки (slicing), да прилагаме методи като .head() и .tail().
Сега да обърнем малко внимание върху устройството на таблицата:
type(data)
Както виждате, pandas предлага за таблици (за двумерни масиви от данни) типа DataFrame. И това е май най-често използваният тип. Защото pandas предлага още типа Panel за тримерни масиви от данни, а чрез модула xarray могат да се създават и многомерни масиви от данни.
Не ми се е налагало да работя с многомерни масиви и няма да ви занимавам с това. Пък вие, ако им намерите приложение в глотометрията, ще показвате.
Както виждате, редовете в таблицата се номерират автоматично. Тази служебна колона наричат индекс. При това номерацията започва от нула. Типът DataFrame извършва индексирането „автоматично“, когато създава таблицата. Но практически всяка колона може да бъде превърната в индекс, съвсем не е задължително индексът да е от числа и не е задължително да са поредни, като повторенията също са допустими. Индексът в момента можем да видим така:
data.index
Вижда се, че той е подобен на много използвания в питона обект range().
Имената на колоните са също доста сходни с индекса (видяхте, че също могат да бъдат автоматично номерирани, пак от 0). Сега те изглеждат така:
data.columns
И индексът, и имената на колоните се използват, разбира се, за да се идентифицира определено поле (или по-общо — обект) от таблицата. Така колоните са от типа серия:
type(data.lichno)
data.lichno
Серията притежава индекс, но може да бъде и изнесена просто като списък (list):
print(data.lichno.tolist()[:5])
С индекса може да изберете ред
data.loc[[3], :]
или отделно поле
data.loc[[3], ['familno']]
С метода .iloc (аз допусках, че i е от index, но го видях изведено и от integer) се постига същото, но и по редове, и по колони трябва да се използва числовия индекс:
data.iloc[3, 2]
По този начин полето може и да се редактира. Само че трябва да предупредя: редакции в таблица на модула pandas се правят бавно и тромаво. Модулът е оптимизиран бързо да извлича и да обработва данните, но не и да ги редактира. Ако се налагат повече редакции в данните, направете ги в текстовия файл и създайте отново таблица от типа DataFrame.
Сега остава да запишем новосъздадената таблица във файл.
Pandas работи хубаво и лесно с бинарния „научен“ файлов формат .hdf5 (Hierarchical Data Format). Точно заради него препоръчах да инсталирате с conda модулите hdf5 и pytables ???.
Записът става така:
data.to_hdf('all_data.hdf5', key='data', mode='w')
Създаденият файл е около една трета от размера на изходния текстов файл (all_text.csv) и се изчита също значително по-бързо. Разширението .hdf5 не е задължително, ама е много полезно, за да виждате какво сте записали в този файл.
Параметърът key дава име на съответния набор от данни. Работата е там, че този файлов формат всъщност представлява контейнер за данни, за различни таблици, които може да събирате в един файл, а когато четете данните, чрез името, което поставяте в key, ще определяте коя таблица да изчетете.
Точно затова и подразбиращият се режим на работа с файла е mode='a', тоест append, дописване във файла. Ако редактираме нашите данни и ги запишем отново във файла, те също ще бъдат добавени, а няма да подменят предишните данни, дори ако ги записваме със същия ключ 'data'.
С mode='w' от write файлът се създава отново, а не се дописва и не се раздува на големина. Но какво става, ако във файла е имало и други данни? Трябва да ги добавите отново…
Да се използва този файлов формат като контейнер сигурно е удобно, ако имате различни данни, които само четете от файла, но не редактирате и не преписвате.
Ама аз често нанасям по някоя и друга поправчица, затова предпочитам всеки набор от данни да записвам в отделен файл и използвам mode='w', за да не раздувам файла излишно.
Следващия път — повече.
10 minutes to pandas — това е страница от официалната документация на pandas и сигурно 10 минути няма да ви стигнат. Има много примери.
Data School's top 25 pandas tricks — изключително полезна страница на Kevin Markham, с много и разнообразни примери, включително с графики.
Pandas. A Complecte Introduction — страница на George McIntire, Brendan Martin и Lauren Washington: доста по-разказвателна страница в сравнение с предишните две, но това не е недостатък; разглежда се четене на данни и от други файлови формат; има и повече за работа с текстова информация.
Using iloc, loc, & ix to select rows and columns in Pandas DataFrames — на тази страница Shane Linn подробно и с доста примери разяснява използването на методите .loc(), .iloc() и непрепоръчвания вече -ix(). Накрая има пример за редактиране на таблица от типа DataFrame. Полезно е да се погледната и другите неща в раздела Pandas Tutorials.
Working with missing data — страница от официалната документация на pandas, където се обсъжда работата с NaN, с NA (not available, тоест, липсващи данни) и с None.
Null and missing data in Python — В тази статия на Aleksey Bilogur се разглеждат различни по вид „липсващи“ данни с хубави илюстриращи примери. В последната част на статията се обсъжда как в Python и в pandas могат да се отразяват тези видове „липсващи“ данни.
И накрая — Google. Търсете, например, „pandas examples“.