Структурні операції та мультиіндексні об'єкти у DataFrame

Структури даних у pandas


Ядром pandas є дві структури даних, у яких відбуваються всі операції:

Series- це структура, яка використовується для роботи з послідовністю одновимірних даних, а Dataframe- більш складна і підходить для кількох вимірів.

Нехай вони не є універсальними для вирішення всіх проблем, надають відмінний інструмент для більшості додатків. При цьому їх легко використовувати, а безліч складніших структур можна просити до однієї з цих двох.

Однак особливості цих структур засновані на одній межі - інтеграції в їхню структуру об'єктів indexі labels(мітки). З їхньою допомогою структурами стає дуже легко маніпулювати.

Series (серії)

Series- це об'єкт бібліотеки pandas , спроектований для представлення одномірних структур даних, схожих на масиви, але з додатковими можливостями. Його структура проста, адже вона складається з двох пов'язаних між собою масивів. Основний містить дані (дані будь-якого типу NumPy), а додатковому, index, зберігаються мітки.

Створення об'єкта Series

Для створення об'єкта Seriesз попереднього зображення необхідно викликати конструктор Series()і передати як аргумент масив, що містить значення, які потрібно включити.

>>> s = pd.Series([12,-4,7,9])

>>> s

0    12

1    -4

2     7

3     9

dtype: int64


Як можна побачити з висновку, зліва відображаються значення індексів, а праворуч самі значення (дані).

Якщо не визначити індекс під час оголошення об'єкта, мітки відповідатимуть індексам (положенню в масиві) елементів об'єкта Series.

Однак краще створювати Series, використовуючи мітки з певним змістом, щоб у майбутньому відокремлювати та ідентифікувати дані незалежно від того, в якому порядку вони зберігаються.

У такому разі необхідно буде при виклику конструктора включити параметр indexі привласнити масив рядків з мітками.

>>> s = pd.Series([12,-4,7,9], index=['a','b','c','d'])

>>> s

a    12

b    -4

c     7

d     9

dtype: int64


Якщо потрібно побачити обидва масиви, з яких складається структура, можна викликати два атрибути: indexі values.

>>> s.values

array([12, -4, 7, 9], dtype=int64)

>>> s.index

Index(['a', 'b', 'c', 'd'], dtype='object')

Вибір елементів за індексом чи міткою

Вибирати окремі елементи можна за принципом звичайних масивів numpy, використовуючи при цьому індекс.

>>> s[2] 

7


Або ж можна вибрати мітку, що відповідає положенню індексу.

>>> s['b'] 

-4


Так само можна вибрати кілька елементів масиву numpy за допомогою наступної команди:

>>> s[0:2]

a    12

b    -4

dtype: int64

У цьому випадку можна використовувати відповідні мітки, але вказати їх список у масиві.

>>> s[['b','c']]

b   -4

c    7

dtype: int64

Надання значень елементам

Розуміючи, як вибирати окремі елементи, важливо знати і те, як надавати їм нові значення. Можна робити це за індексом або міткою.

>>> s[1] = 0 

>>> s

a    12

b     0

c     7

d     9

dtype: int64


>>> s['b'] = 1

>>> s

a    12

b     1

c     7

d     9

dtype: int64


Створення Series із масивів NumPy

Новий об'єкт Seriesможна створити з масивів NumPy і вже існуючих Series.

>>> arr = np.array([1,2,3,4])

>>> s3 = pd.Series(arr)

>>> s3

0    1

1    2

2    3

3    4

dtype: int32


>>> s4 = pd.Series(s)

>>> s4

a    12

b     1

c     7

d     9

dtype: int64


Важливо запам'ятати, що значення масиві NumPy або оригінальному об'єкті Seriesне копіюються, а передаються за посиланням. Це означає, що елементи об'єкта динамічно вставляються в новий Series. Якщо змінюється оригінальний об'єкт, то змінюються його значення в новому.

>>> s3

0    1

1    2

2    3

3    4

dtype: int32


>>> arr[2] = -2 

>>> s3

0    1

1    2

2    -2

3    4

dtype: int32


На цьому прикладі можна побачити, що при зміні третього елемента масиву arrзмінюється відповідний елемент і в s3.

Фільтрування значень

Завдяки тому, що основною бібліотекою в pandas є NumPy, багато операцій, що застосовуються до масивів NumPy, можуть бути використані і у випадку з Series. Однією з таких є фільтрація значень у структурі даних за допомогою умов.

Наприклад, якщо потрібно дізнатися, які елементи Seriesбільше 8, то можна написати наступне:

>>> s[s > 8]

a    12

d     9

dtype: int64


Операції та математичні функції

Інші операції, такі як оператори ( +, -, *і /), а також математичні функції, що працюють з масивами NumPy можуть використовуватися і для Series.

Для операторів можна написати просте арифметичне рівняння.

>>> s / 2

a    6.0

b    0.5

c    3.5

d    4.5

dtype: float64


Але у випадку з математичними функціями NumPy необхідно вказати функцію через np, а Seriesпередати як аргумент.

>>> np.log(s)

a    2.484907

b    0.000000

c    1.945910

d    2.197225

dtype: float64


Кількість значень

У Seriesнайчастіше зустрічаються повторення значень. Тому важливо мати інформацію, яка вказувала б на те, чи є дублікати чи конкретне значення в об'єкті.

Так, можна оголосити Series, в якому будуть повторювані значення.

>>> serd = pd.Series([1,0,2,1,2,3], index=['white','white','blue','green',' green','yellow']) 

>>> serd

white     1

white     0

blue      2

green     1

green     2

yellow    3

dtype: int64


Щоб дізнатися про всі значення в Series, не включаючи дублі, можна використовувати unique(). Значення, що повертається - масив з унікальними значеннями, необов'язково в тому ж порядку.

>>> serd.unique() 

array([1, 0, 2, 3], dtype=int64)


На unique()схожа функція value_counts(), яка повертає як унікальне значення, а й показує, як часто елементи зустрічаються в Series.

>>> serd.value_counts()

2    2

1    2

3    1

0    1

dtype: int64


Нарешті, isin()показує, чи є елементи з урахуванням списку значень. Вона повертає булеві значення, які дуже корисні при фільтрації даних Seriesабо в колонці Dataframe.


>>> serd.isin([0,3])

white     False

white      True

blue      False

green     False

green     False

yellow     True

dtype: bool


>>> serd[serd.isin([0,3])]

white     0

yellow    3

dtype: int64

Значення NaN

У попередньому прикладі ми спробували отримати логарифм негативного числа і результатом стало значення NaN. Це значення (Not a Number) використовується у структурах даних pandas для позначення наявності порожнього поля або чогось, що неможливо позначити у числовій формі.

Як правило, NaNце проблема, для якої потрібно знайти певне рішення, особливо при роботі з аналізом даних. Ці дані часто з'являються при вилученні інформації з неперевірених джерел або коли в самому джерелі даних немає. Також значення NaNможуть генеруватися у спеціальних випадках, наприклад, при обчисленні логарифмів для негативних значень, у разі виключень під час обчислень або використання функцій. Є різні стратегії роботи зі значеннями NaN.

Незважаючи на свою «проблемність» pandas дозволяє явно визначати NaNі додавати це значення до структур, наприклад, в Series. Для цього всередині масиву достатньо ввести np.NaNв тому місці, де потрібно визначити значення, що бракує.

>>> s2 = pd.Series([5,-3,np.NaN,14]) 

>>> s2

0     5.0

1    -3.0

2     NaN

3    14.0

dtype: float64


Функції isnull()і notnull()дуже корисні визначення індексів без значення.

>>> s2.isnull()

0    False

1    False

2     True

3    False

dtype: bool


>>> s2.notnull()

0     True

1     True

2    False

3     True

dtype: bool


Вони повертають два об'єкти Seriesз булевими значеннями , де Trueвказує на наявність значення, а NaN- на його відсутність. Функція isnull()повертає Trueдля значень NaNу Series, а notnull()- Trueу тих місцях, де значення не дорівнює NaN. Ці функції часто використовуються у фільтрах для створення умов.

>>> s2[s2.notnull()]

0     5.0

1    -3.0

3    14.0

dtype: float64


s2[s2.isnull()]

2   NaN

dtype: float64


Series зі словників

Seriesможна сприймати як об'єкт dict(словник). Ця схожість може бути використана на етапі оголошення об'єкта. Навіть створювати Seriesможна на основі існуючого dict.

>>> mydict = {'red': 2000, 'blue': 1000, 'yellow': 500,

 'orange': 1000}

>>> myseries = pd.Series(mydict)

>>> myseries

blue      1000

orange    1000

red       2000

yellow     500

dtype: int64


На цьому прикладі можна побачити, що масив індексів заповнений ключами, а дані відповідними значеннями. У такому випадку співвідношення буде встановлено між ключами dictта мітками масиву індексів. Якщо є невідповідність, pandas замінить його на NaN.

>>> colors = ['red','yellow','orange','blue','green']

>>> myseries = pd.Series(mydict, index=colors)

>>> myseries

red       2000.0

yellow     500.0

orange    1000.0

blue      1000.0

green        NaN

dtype: float64


Операції із серіями

Ви вже бачили, як виконати арифметичні операції на об'єктах Seriesта скалярних величинах. Те ж можливе і для двох об'єктів Series, але в такому випадку в справу вступають і мітки.

Одне з головних переваг цього типу структур даних у цьому, що може вирівнювати дані, визначаючи відповідні мітки.

У наступному прикладі додаються два об'єкти Series, у яких лише деякі мітки збігаються.

>>> mydict2 = {'red':400,'yellow':1000,'black':700}

>>> myseries2 = pd.Series(mydict2)

>>> myseries + myseries2

black        NaN

blue         NaN

green        NaN

orange       NaN

red       2400.0

yellow    1500.0

dtype: float64


Новий об'єкт отримує лише ті елементи, де мітки збіглися. Решта теж присутні, але зі значенням NaN.

DataFrame (датафрейм)

Dataframe— це таблична структура даних, що нагадує таблиці Microsoft Excel. Її головне завдання – дозволити використовувати багатовимірні Series. Dataframeскладається з упорядкованої колекції колонок, кожна з яких містить значення різних типів (числове, рядкове, булеве тощо).

На відміну від Seriesякого є масив індексів з мітками, асоційованих з кожним з елементів, Dataframeмає відразу два таких. Перший асоційований з рядками (рядами) і нагадує такий з Series. Кожна мітка асоційована з усіма значеннями у ряду. Другий містить мітки для кожної з колонок.

Dataframeможна сприймати як dict, що складається з Series, де ключі - назви колонок, а значення - об'єкти Series, які формують колонки самого об'єкта Dataframe. Нарешті, усі елементи у кожному об'єкті Seriesпов'язані відповідно до масивом міток, званим index.

Створення Dataframe

Найпростіший спосіб створення Dataframe– передати об'єкт dictу конструктор DataFrame(). Об'єкт dictмістить ключ кожної колонки, яку потрібно визначити, і навіть масив значень їм.

Якщо об'єкт dictмістить більше даних, ніж потрібно, можна зробити вибірку. Для цього в конструкторі Dataframeпотрібно визначити послідовність колонок за допомогою параметра column. Колонки будуть створені в заданому порядку, незалежно від того, як вони розташовані в об'єкті dict.


>> data = {'color' : ['blue', 'green', 'yellow', 'red', 'white'],

        'object' : ['ball', 'pen', 'pencil', 'paper', 'mug'],

        'price' : [1.2, 1.0, 0.6, 0.9, 1.7]}

>>> frame = pd.DataFrame(data)

>>> frame

Навіть для об'єктів Dataframeякщо мітки явно не задані в масиві index, pandas автоматично надає числову послідовність, починаючи з нуля. Якщо індексам Dataframeпотрібно присвоїти мітки, необхідно використовувати параметр indexі присвоїти йому масив з мітками.

>>> frame2 = pd.DataFrame(data, columns=['object', 'price'])

>>> frame2


Тепер, знаючи параметри indexі columns, простіше використовувати інший спосіб визначення Dataframe. Замість використання об'єкта dictможна визначити три аргументи в конструкторі в наступному порядку: матрицю даних, масив значень параметра indexі масив з назвами колонок параметра columns.

Найчастіше найпростіший спосіб створити матрицю значень — використовувати np.arrange(16).reshape((4,4)). Це формує матрицю розміром 4х4 із чисел від 0 до 15.

>>> frame3 = pd.DataFrame(np.arange(16).reshape((4,4)),

...                       index=['red', 'blue', 'yellow', 'white'],

...                       columns=['ball', 'pen', 'pencil', 'paper'])

>>> frame3


Вибір елементів

Якщо потрібно дізнатися назви всіх колонок Dataframe, можна викликати атрибут columnsдля екземпляра об'єкта.

>>> frame.columns

Index(['color', 'object', 'price'], dtype='object')


Те саме можна зробити і для отримання списку індексів.

>>> frame.index

RangeIndex(start=0, stop=5, step=1)


Весь набір даних можна отримати за допомогою атрибута values.

>>> frame.values

array([['blue', 'ball', 1.2],

       ['green', 'pen', 1.0],

       ['yellow', 'pencil', 0.6],

       ['red', 'paper', 0.9],

       ['white', 'mug', 1.7]], dtype=object)


Вказавши в квадратних дужках назву колонки, можна отримати значень у ній.

>>> frame['price']

0    1.2

1    1.0

2    0.6

3    0.9

4    1.7

Name: price, dtype: float64


Значення, що повертається - об'єкт Series. Назва колонки можна використовувати і як атрибут.

>>> frame.price

0    1.2

1    1.0

2    0.6

3    0.9

4    1.7

Name: price, dtype: float64


Для рядків усередині Dataframeвикористовується атрибут locзі значенням індексу потрібного рядка.

>>> frame.loc[2]

color     yellow

object    pencil

price        0.6

Name: 2, dtype: object


Об'єкт, що повертається - це знову Series, де назви колонок - це вже мітки масиву індексів, а значення - дані Series.

Для вибору кількох рядків можна вказати масив із їх послідовністю.

>>> frame.loc[[2,4]]

Якщо необхідно витягти частину Dataframeз конкретними рядками, можна використовувати номери індексів. Вона виведе дані з відповідного рядка та назви колонок.

>>> frame[0:1]

Нарешті, якщо необхідно отримати одне значення з об'єкта, спочатку потрібно вказати назву колонки, а потім індекс або мітку рядка.

>>> frame['object'][3]

'paper'


Присвоєння та заміна значень

Розібравшись з логікою отримання доступу до різних елементів Dataframe, можна слідувати їй для додавання нових або зміни вже існуючих значень.

Наприклад, у структурі Dataframeмасив індексів визначено атрибутом index, а рядок із назвами колонок — columns. Можна призначити мітку за допомогою атрибута nameцих двох підструктур, щоб ідентифікувати їх.

>>> frame.index.name = 'id'

>>> frame.columns.name = 'item'

>>> frame


Одна з головних особливостей структур даних pandas - їхня гнучкість. Можна втрутитися будь-якому рівні зміни внутрішньої структури даних. Наприклад, додавання нової колонки – вкрай поширена операція.

Її можна виконати, надавши значення екземпляру Dataframeі визначивши нове ім'я колонки.

>>> frame['new'] = 12 

>>> frame

Тут видно, що з'явилася нова колонка newзі значеннями 12 кожного елемента.

Для оновлення значень можна використовувати масив.

frame['new'] = [3.0, 1.3, 2.2, 0.8, 1.1]

frame

Той самий підхід використовується для оновлення цілої колонки. Наприклад, можна застосувати функцію np.arrange()для оновлення значень колонки за допомогою заздалегідь заданої послідовності.

Колонки Dataframeтакож можуть бути створені за допомогою привласнення об'єкта Seriesоднієї з них, наприклад, визначивши об'єкт Series, що містить набір значень, що збільшуються за допомогою np.arrange().

>>> ser = pd.Series(np.arange(5)) 

>>> ser

0    0

1    1

2    2

3    3

4    4

dtype: int32


frame['new'] = ser

frame

Нарешті, зміни одного значення потрібно лише вибрати елемент і привласнити йому нове значення.


>>> frame['price'][2] = 3.3


Входження значень

Функція isin()використовується з об'єктами Seriesвизначення входження значень в колонку. Вона ж підходить і для об'єктів Dataframe.

>>> frame.isin([1.0,'pen'])

Повертається Dataframeз булевими значеннями, де Trueвказує ті значення, де членство підтверджено. Якщо передати це значення як умови, тоді повернеться Dataframe, де будуть лише значення, задовольняють умові.

>>> frame[frame.isin([1.0,'pen'])]

Видалення колонки

Для видалення цілої колонки та всього її вмісту використовується команда del.

>>> del frame['new'] 

>>> frame


Фільтрування

Навіть Dataframeможна використовувати фільтри, використовуючи певні умови. Наприклад, вам потрібно отримати всі значення менше певного числа (припустимо, 1,2).

>>> frame[frame  <  1.2]

Результатом буде Dataframeзі значеннями менше ніж 1,2 на своїх місцях. На місці решти буде NaN.

Dataframe з вкладеного словника

У Python часто використовується вкладений dict:

>>> nestdict = {'red': { 2012: 22, 2013: 33},

...             'white': { 2011: 13, 2012: 22, 2013: 16},

...             'blue': { 2011: 17, 2012: 27, 2013: 18}}


Ця структура даних, будучи переданою як аргумент DataFrame(), інтерпретується pandas так, що зовнішні ключі стають назвами колонок, а внутрішні - мітками індексів.

При інтерпретації вкладеної структури можливе таке, що не всі поля збігатимуться. pandas компенсує цю невідповідність, додаючи NaNна місце значень, що відсутні.

>>> nestdict = {'red': { 2012: 22, 2013: 33},

...             'white': { 2011: 13, 2012: 22, 2013: 16},

...             'blue': { 2011: 17, 2012: 27, 2013: 18}}

>>> frame2 = pd.DataFrame(nestdict)

>>> frame2


Транспонування Dataframe

p align="justify"> При роботі з табличним структурами даних іноді з'являється необхідність виконати операцію перестановки (зробити так, щоб колонки стали рядами і навпаки). pandas дозволяє досягти цього дуже просто. Достатньо додати атрибут T.

>>> frame2.T


Об'єкти Index

Знаючи, що таке Seriesі Dataframes, і розуміючи як вони влаштовані, простіше розібратися з усіма їхніми перевагами. Головна особливість цих структур - наявність об'єкта Index, який у них інтегрований.

Об'єкти Indexє мітками осей і містять інші метадані. Ви вже знаєте, як масив з мітками перетворюється на об'єкт Indexі що для нього потрібно визначити параметр indexу конструкторі.

>>> ser = pd.Series([5,0,3,8,4], index=['red','blue','yellow','white','green'])

>>> ser.index

Index(['red', 'blue', 'yellow', 'white', 'green'], dtype='object')


На відміну від інших елементів у структурах даних pandas ( Seriesі Dataframe) об'єкти index- незмінні. Це забезпечує безпеку, коли необхідно передавати дані між різними структурами.

У кожного об'єкта Indexє методи та властивості, які потрібні, щоб пізнавати значення.

Методи Index

Є методи отримання інформації про індекси з структури даних. Наприклад, idmin()і idmax()- структури, що повертають індекси з найменшим і більшим значеннями.

>>> ser.idxmin()

'blue'

>>> ser.idxmax()

'white'


Індекс з мітками, що повторюються

Поки що були лише ті випадки, коли індекси однієї структури мають лише одну, унікальну мітку. Для більшості функцій це обов'язкова умова, але не для структур даних пандас.

Визначимо, наприклад, Seriesз мітками, що повторюються.

>>> serd = pd.Series(range(6), index=['white','white','blue','green', 'green','yellow']) 

>>> serd

white     0

white     1

blue      2

green     3

green     4

yellow    5

dtype: int64


Якщо мітці відповідає кілька значень, вона поверне не один елемент, а об'єкт Series.

>>> serd['white']

white    0

white    1

dtype: int64


Те саме можна застосувати і до Dataframe. При індексах, що повторюються, він повертає Dataframe.

У випадку з маленькими структурами легко визначати будь-які індекси, що повторюються, але якщо структура велика, то зростає і складність цієї операції. Для цього в pandas об'єкти Indexмають атрибут is_unique. Він повідомляє, чи є індекси з мітками, що повторюються, в структурі ( Seriesабо Dataframe).

>>> serd.index.is_unique

False

>>> frame.index.is_unique

True


Операції між структурами даних

Тепер коли ви знайомі зі структурами даних, Seriesа Dataframeтакож базовими операціями для роботи з ними, варто розглянути операції, що включають дві або більше структур.

Гнучкі арифметичні методи

Вже розглянуті операції можна виконувати за допомогою гнучких арифметичних методів:

Для їхнього виклику потрібно використовувати іншу специфікацію. Наприклад, замість того щоб виконувати операцію для двох об'єктів Dataframeза прикладом frame1+ frame2, буде потрібно наступний формат:

>>> frame1.add(frame2)


Результат такий самий, як при використанні оператора додавання + . Також варто звернути увагу, що якщо назви індексів та колонок сильно відрізняються, то результатом стане новий об'єкт Dataframe, що складається тільки із значень NaN.

Операції між Dataframe та Series

Pandas дозволяє виконувати переноси між різними структурами, наприклад між Dataframeі Series. Визначити дві структури можна в такий спосіб.

>>> frame = pd.DataFrame(np.arange(16).reshape((4,4)),

...           index=['red', 'blue', 'yellow', 'white'],

...           columns=['ball','pen','pencil','paper'])

>>> frame

>>> ser = pd.Series(np.arange(4), index=['ball','pen','pencil','paper']) 

>>> ser

ball      0

pen       1

pencil    2

paper     3

dtype: int32


Вони були спеціально створені так, щоб індекси Seriesзбігалися з назвами колонок в Dataframe. У такому разі можна виконати пряму операцію.

>>> frame - ser


За результатом видно, що елементи Seriesбули відняти з відповідних тому ж індексу в колонках значень Dataframe.

Якщо індекс не представлений в жодній структурі, то з'явиться нова колонка з цим індексом і значеннями NaN.

>>> ser['mug'] = 9 

>>> ser

ball      0

pen       1

pencil    2

paper     3

mug       9

dtype: int64


>>> frame - ser