Примітки для викладача
General Notes
- It’s all right not to get through the whole lesson.
- This lesson is designed for people who have never programmed before, but any given class may include people with a wide range of prior experience. We have therefore included enough material to fill a full day if need be, but expect that many offerings will only get as far as the introduction to Pandas.
- Don’t tell people to Google things.
- One of the goals of this lesson is to help novices build a workable mental model of how programming works. Until they have that model, they will not know what to search for or how to recognize a helpful answer. Пропозиція пошукати відповідь в Google може створити враження, що ми вважаємо їх проблему тривіальною. (Однак, якщо учні раніше достатньо займались програмуванням, щоб подолати ці проблеми, підштовхування їх шукати рішення в Інтернеті може допомогти їм зміцнити своє розуміння.) It’s also worth quoting Trevor King’s comment about online search: “If you find anything, other folks were confused enough to bother with a blog or Stack Overflow post, so it’s probably not trivial.”
Запуск та завершення роботи
Змінні та присвоєння
Типи даних та їх перетворення
Вбудовані функції та довідка
Ранкова кава
Бібліотеки
Зчитування табличних даних у датафрейми
Датафрейми Pandas
Примітка для викладача
Learners often struggle here, many may not work with financial data
and concepts so they find the example concepts difficult to get their
head around. Однак основна проблема полягає в рядку, який обчислює
wealth_score; цей крок потребує ретельного обговорення:
Він використовує неявне перетворення між булевим і дійсним значенням, яке ще не розглядалося в курсі.
-
Параметр axis=1 також потребує чіткого пояснення.
:::::::::::::::::::::::::::::::::::::::::::::::::
Методи векторизації та операції групування Pandas — це функції, які надають користувачам велику гнучкість для аналізу своїх даних.
Наприклад, скажімо, ми хочемо мати більш чітке уявлення про те, як європейські країни розподілені відповідно до свого ВВП.
- Ми можемо поглянути на ситуацію, поділивши країни на дві групи за роки спостережень: ті, у яких ВВП вище середнього по Європі, і країни з нижчим ВВП.
- Далі ми оцінюємо показник заможності на основі історичних значень (з 1962 по 2007 рік), підраховуючи, скільки разів кожна країна входила до груп із вищим або нижчим ВВП
PYTHON
mask_higher = data > data.mean()
wealth_score = mask_higher.aggregate('sum', axis=1) / len(data.columns)
print(wealth_score)
ВИХІД
country
Albania 0.000000
Austria 1.000000
Belgium 1.000000
Bosnia and Herzegovina 0.000000
Bulgaria 0.000000
Croatia 0.000000
Czech Republic 0.500000
Denmark 1.000000
Finland 1.000000
France 1.000000
Germany 1.000000
Greece 0.333333
Hungary 0.000000
Iceland 1.000000
Ireland 0.333333
Italy 0.500000
Montenegro 0.000000
Netherlands 1.000000
Norway 1.000000
Poland 0.000000
Portugal 0.000000
Romania 0.000000
Serbia 0.000000
Slovak Republic 0.000000
Slovenia 0.333333
Spain 0.333333
Sweden 1.000000
Switzerland 1.000000
Turkey 0.000000
United Kingdom 1.000000
dtype: float64
Нарешті, для кожної групи в таблиці wealth_score ми
підсумовуємо їх (фінансовий) внесок за роки дослідження, використовуючи
ланцюжок методів:
ВИХІД
gdpPercap_1952 gdpPercap_1957 gdpPercap_1962 gdpPercap_1967 \
0.000000 36916.854200 46110.918793 56850.065437 71324.848786
0.333333 16790.046878 20942.456800 25744.935321 33567.667670
0.500000 11807.544405 14505.000150 18380.449470 21421.846200
1.000000 104317.277560 127332.008735 149989.154201 178000.350040
gdpPercap_1972 gdpPercap_1977 gdpPercap_1982 gdpPercap_1987 \
0.000000 88569.346898 104459.358438 113553.768507 119649.599409
0.333333 45277.839976 53860.456750 59679.634020 64436.912960
0.500000 25377.727380 29056.145370 31914.712050 35517.678220
1.000000 215162.343140 241143.412730 263388.781960 296825.131210
gdpPercap_1992 gdpPercap_1997 gdpPercap_2002 gdpPercap_2007
0.000000 92380.047256 103772.937598 118590.929863 149577.357928
0.333333 67918.093220 80876.051580 102086.795210 122803.729520
0.500000 36310.666080 40723.538700 45564.308390 51403.028210
1.000000 315238.235970 346930.926170 385109.939210 427850.333420
Ні, вони не дають однакові результати! Результатом першого виразу є:
ВИХІД
gdpPercap_1952 gdpPercap_1957
country
Albania 1601.056136 1942.284244
Austria 6137.076492 8842.598030
Другий вираз дає:
ВИХІД
gdpPercap_1952 gdpPercap_1957 gdpPercap_1962
country
Albania 1601.056136 1942.284244 2312.888958
Austria 6137.076492 8842.598030 10750.721110
Belgium 8343.105127 9714.960623 10991.206760
Вочевидь, другий вираз повертає додатковий стовпець і додатковий
рядок у порівнянні з першим.
Який висновок ми можемо зробити? Ми бачимо, що числовий зріз 0:2 не
включає кінцевий індекс (тобто, індекс 2), тоді як іменований зріз
‘gdpPercap_1952’:‘gdpPercap_1962’ включає кінцевий елемент.
Реконструювання даних
Опишіть, що робить кожен рядок наведеної короткої програми та які
значення зберігаються в змінних first, second
тощо?
Перегляньмо цей фрагмент коду рядок за рядком.
В цьому рядку дані про ВВП з усіх країн завантажуються у датафрейм
first. Параметр index_col='country' вказує,
який стовпець використовується як заголовки рядків у датафреймі.
Цей рядок фільтрує дані: вибираються лише ті рядки з
first, у яких стовпець ‘continent’ містить значення
‘Americas’. Зверніть увагу, як логічний вираз у дужках,
first['continent'] == 'Americas', використовується для
вибору лише тих рядків, де вираз є істинним. Спробуйте надрукувати цей
вираз! Чи можете ви також надрукувати його окремі елементи True/False?
(підказка: спочатку призначте вираз певній змінній)
Як підказує синтаксис, цей код видаляє рядок з міткою ‘Puerto Rico’ з
датафрейму second. У результаті датафрейм
third містить на один рядок менше, ніж вихідний датафрейм
second.
Знову ми застосовуємо функцію drop, але в цьому випадку
ми видаляємо не рядок, а цілий стовпець. Для цього нам також потрібно
вказати параметр axis (ми хочемо видалити другий стовпець,
який має індекс 1).
Останнім кроком є збереження даних, над якими ми працювали, у файл
csv. Pandas спрощує це завдання за допомогою функції
to_csv(). Єдиним обов’язковим аргументом для функції є ім’я
файлу. Зверніть увагу, що файл буде записано в каталозі, з якого ви
розпочали сесію Jupyter або Python.
Для кожного стовпця в data, idxmin поверне
значення індексу, що відповідає мінімуму кожного стовпця;
idxmax зробить те саме для максимального значення кожного
стовпця.
Ви можете використовувати ці функції щоразу, коли потрібно отримати індекс рядка з мінімальним або максимальним значенням, а не саме значення.
Потренуйтеся з вибором
Припустімо, що Pandas було імпортовано та дані Gapminder про ВВП для Європи завантажено. Напишіть вираз, щоб вибрати кожне з наступного:
- ВВП на душу населення для всіх країн у 1982 році.
- ВВП на душу населення для Данії за всі роки.
- ВВП на душу населення для всіх країн за роки після 1985 року.
- ВВП на душу населення у 2007 році для кожної країни, виражене як кратне до ВВП на душу населення цієї ж країни у 1952 році.
1:
2:
3:
Пакет pandas досить розумний, щоб розпізнати число в кінці заголовку
стовпця й не видати помилки, навіть якщо стовпця з назвою
gdpPercap_1985 не існує. Це зручно, якщо нові стовпці
додаються до файлу CSV пізніше.
4:
Багато способів доступу
Отримати значення або зріз з датафрейму можна щонайменше двома
способами: за назвою або за індексом. Однак існує багато інших
варіантів. Наприклад, можна отримати окремий стовпець або рядок як
DataFrame або Series об’єкт.
Запропонуйте різні способи виконання наступних операцій з датафреймами:
- Доступ до одного стовпця
- Доступ до одного рядку
- Доступ до окремого елемента датафрейму
- Доступ до декількох стовпців
- Доступ до декількох рядків
- Доступ до підмножини, заданої визначеними рядками та стовпцями
- Доступ до підмножини, заданої діапазонами рядків та стовпців
1. Доступ до одного стовпця:
PYTHON
# by name
data["col_name"] # as a Series
data[["col_name"]] # as a DataFrame
# by name using .loc
data.T.loc["col_name"] # as a Series
data.T.loc[["col_name"]].T # as a DataFrame
# Dot notation (Series)
data.col_name
# by index (iloc)
data.iloc[:, col_index] # as a Series
data.iloc[:, [col_index]] # as a DataFrame
# using a mask
data.T[data.T.index == "col_name"].T
2. Доступ до одного рядку:
PYTHON
# by name using .loc
data.loc["row_name"] # as a Series
data.loc[["row_name"]] # as a DataFrame
# by name
data.T["row_name"] # as a Series
data.T[["row_name"]].T # as a DataFrame
# by index
data.iloc[row_index] # as a Series
data.iloc[[row_index]] # as a DataFrame
# using mask
data[data.index == "row_name"]
3. Доступ до окремого елемента датафрейму:
PYTHON
# by column/row names
data["column_name"]["row_name"] # as a Series
data[["col_name"]].loc["row_name"] # as a Series
data[["col_name"]].loc[["row_name"]] # as a DataFrame
data.loc["row_name"]["col_name"] # as a value
data.loc[["row_name"]]["col_name"] # as a Series
data.loc[["row_name"]][["col_name"]] # as a DataFrame
data.loc["row_name", "col_name"] # as a value
data.loc[["row_name"], "col_name"] # as a Series. Preserves index. Column name is moved to `.name`.
data.loc["row_name", ["col_name"]] # as a Series. Index is moved to `.name.` Sets index to column name.
data.loc[["row_name"], ["col_name"]] # as a DataFrame (preserves original index and column name)
# by column/row names: Dot notation
data.col_name.row_name
# by column/row indices
data.iloc[row_index, col_index] # as a value
data.iloc[[row_index], col_index] # as a Series. Preserves index. Column name is moved to `.name`
data.iloc[row_index, [col_index]] # as a Series. Index is moved to `.name.` Sets index to column name.
data.iloc[[row_index], [col_index]] # as a DataFrame (preserves original index and column name)
# column name + row index
data["col_name"][row_index]
data.col_name[row_index]
data["col_name"].iloc[row_index]
# column index + row name
data.iloc[:, [col_index]].loc["row_name"] # as a Series
data.iloc[:, [col_index]].loc[["row_name"]] # as a DataFrame
# using masks
data[data.index == "row_name"].T[data.T.index == "col_name"].T
4. Доступ до декількох стовпців:
PYTHON
# by name
data[["col1", "col2", "col3"]]
data.loc[:, ["col1", "col2", "col3"]]
# by index
data.iloc[:, [col1_index, col2_index, col3_index]]
5. Доступ до декількох рядків
PYTHON
# by name
data.loc[["row1", "row2", "row3"]]
# by index
data.iloc[[row1_index, row2_index, row3_index]]
6. Доступ до підмножини, заданої визначеними рядками та стовпцями
PYTHON
# by names
data.loc[["row1", "row2", "row3"], ["col1", "col2", "col3"]]
# by indices
data.iloc[[row1_index, row2_index, row3_index], [col1_index, col2_index, col3_index]]
# column names + row indices
data[["col1", "col2", "col3"]].iloc[[row1_index, row2_index, row3_index]]
# column indices + row names
data.iloc[:, [col1_index, col2_index, col3_index]].loc[["row1", "row2", "row3"]]
7. Доступ до підмножини, заданої діапазонами рядків та стовпців
PYTHON
# by name
data.loc["row1":"row2", "col1":"col2"]
# by index
data.iloc[row1_index:row2_index, col1_index:col2_index]
# column names + row indices
data.loc[:, "col1_name":"col2_name"].iloc[row1_index:row2_index]
# column indices + row names
data.iloc[:, col1_index:col2_index].loc["row1":"row2"]
Пошук доступних методів використовуючи функцію
dir()
У Python є функція dir(), яка дозволяє переглянути всі
доступні методи (функції), які вбудовані в об’єкт даних. В епізоді 4 ми
використали деякі методи для рядка. Але за допомогою dir()
можна побачити ще більше доступних методів:
Ця команда повертає:
PYTHON
['__add__',
...
'__subclasshook__',
'capitalize',
'casefold',
'center',
...
'upper',
'zfill']
Ви можете використати help() або
Shift+Tab, щоб дізнатися більше про призначення
цих методів.
Припустимо, що Pandas вже імпортовано, а дані Gapminder про ВВП
Європи завантажено в змінну data. Тоді скористайтеся
dir(), щоб знайти функцію, яка друкує середній ВВП на душу
населення для всіх європейських країн за кожен доступний рік.
Інтерпретація даних
Кордони Польщі стабільні з 1945 року, але до цього кілька разів змінювалися. Як би ви врахували це при створенні таблиці ВВП на душу населення для Польщі за все ХХ століття?
- Використовуйте
DataFrame.iloc[..., ...]для вибору значень за їх позицією - Використовуйте лише
:замість міток для позначення всіх стовпців або всіх рядків. - Вибирайте кілька стовпців або рядків за допомогою
DataFrame.locта зрізу, заданого іменами стовпців або рядків. - Результат застосування операції зрізу може бути використаний у подальших операціях.
- Використовуйте порівняння для вибору даних на основі їх значень.
- Вибирайте значення або NaN за допомогою булевої маски.
Вибір можна зробити за допомогою мітки “Сербія” для рядка, та мітки “gdpPercap_2007” для стовпця:
Результат є таким
ВИХІД
9786.534714
Ні, вони не дають однакові результати! Результатом першого виразу є:
ВИХІД
gdpPercap_1952 gdpPercap_1957
country
Albania 1601.056136 1942.284244
Austria 6137.076492 8842.598030
Другий вираз дає:
ВИХІД
gdpPercap_1952 gdpPercap_1957 gdpPercap_1962
country
Albania 1601.056136 1942.284244 2312.888958
Austria 6137.076492 8842.598030 10750.721110
Belgium 8343.105127 9714.960623 10991.206760
Вочевидь, другий вираз повертає додатковий стовпець і додатковий
рядок у порівнянні з першим.
Який висновок ми можемо зробити? Ми бачимо, що числовий зріз 0:2 не
включає кінцевий індекс (тобто, індекс 2), тоді як іменований зріз
‘gdpPercap_1952’:‘gdpPercap_1962’ включає кінцевий елемент.
Перегляньмо цей фрагмент коду рядок за рядком.
В цьому рядку дані про ВВП з усіх країн завантажуються у датафрейм
first. Параметр index_col='country' вказує,
який стовпець використовується як заголовки рядків у датафреймі.
Цей рядок фільтрує дані: вибираються лише ті рядки з
first, у яких стовпець ‘continent’ містить значення
‘Americas’. Зверніть увагу, як логічний вираз у дужках,
first['continent'] == 'Americas', використовується для
вибору лише тих рядків, де вираз є істинним. Спробуйте надрукувати цей
вираз! Чи можете ви також надрукувати його окремі елементи True/False?
(підказка: спочатку призначте вираз певній змінній)
Як підказує синтаксис, цей код видаляє рядок з міткою ‘Puerto Rico’ з
датафрейму second. У результаті датафрейм
third містить на один рядок менше, ніж вихідний датафрейм
second.
Знову ми застосовуємо функцію drop, але в цьому випадку
ми видаляємо не рядок, а цілий стовпець. Для цього нам також потрібно
вказати параметр axis (ми хочемо видалити другий стовпець,
який має індекс 1).
Останнім кроком є збереження даних, над якими ми працювали, у файл
csv. Pandas спрощує це завдання за допомогою функції
to_csv(). Єдиним обов’язковим аргументом для функції є ім’я
файлу. Зверніть увагу, що файл буде записано в каталозі, з якого ви
розпочали сесію Jupyter або Python.
Для кожного стовпця в data, idxmin поверне
значення індексу, що відповідає мінімуму кожного стовпця;
idxmax зробить те саме для максимального значення кожного
стовпця.
Ви можете використовувати ці функції щоразу, коли потрібно отримати індекс рядка з мінімальним або максимальним значенням, а не саме значення.
1:
2:
3:
Пакет pandas досить розумний, щоб розпізнати число в кінці заголовку
стовпця й не видати помилки, навіть якщо стовпця з назвою
gdpPercap_1985 не існує. Це зручно, якщо нові стовпці
додаються до файлу CSV пізніше.
4:
1. Доступ до одного стовпця:
PYTHON
# by name
data["col_name"] # as a Series
data[["col_name"]] # as a DataFrame
# by name using .loc
data.T.loc["col_name"] # as a Series
data.T.loc[["col_name"]].T # as a DataFrame
# Dot notation (Series)
data.col_name
# by index (iloc)
data.iloc[:, col_index] # as a Series
data.iloc[:, [col_index]] # as a DataFrame
# using a mask
data.T[data.T.index == "col_name"].T
2. Доступ до одного рядку:
PYTHON
# by name using .loc
data.loc["row_name"] # as a Series
data.loc[["row_name"]] # as a DataFrame
# by name
data.T["row_name"] # as a Series
data.T[["row_name"]].T # as a DataFrame
# by index
data.iloc[row_index] # as a Series
data.iloc[[row_index]] # as a DataFrame
# using mask
data[data.index == "row_name"]
3. Доступ до окремого елемента датафрейму:
PYTHON
# by column/row names
data["column_name"]["row_name"] # as a Series
data[["col_name"]].loc["row_name"] # as a Series
data[["col_name"]].loc[["row_name"]] # as a DataFrame
data.loc["row_name"]["col_name"] # as a value
data.loc[["row_name"]]["col_name"] # as a Series
data.loc[["row_name"]][["col_name"]] # as a DataFrame
data.loc["row_name", "col_name"] # as a value
data.loc[["row_name"], "col_name"] # as a Series. Preserves index. Column name is moved to `.name`.
data.loc["row_name", ["col_name"]] # as a Series. Index is moved to `.name.` Sets index to column name.
data.loc[["row_name"], ["col_name"]] # as a DataFrame (preserves original index and column name)
# by column/row names: Dot notation
data.col_name.row_name
# by column/row indices
data.iloc[row_index, col_index] # as a value
data.iloc[[row_index], col_index] # as a Series. Preserves index. Column name is moved to `.name`
data.iloc[row_index, [col_index]] # as a Series. Index is moved to `.name.` Sets index to column name.
data.iloc[[row_index], [col_index]] # as a DataFrame (preserves original index and column name)
# column name + row index
data["col_name"][row_index]
data.col_name[row_index]
data["col_name"].iloc[row_index]
# column index + row name
data.iloc[:, [col_index]].loc["row_name"] # as a Series
data.iloc[:, [col_index]].loc[["row_name"]] # as a DataFrame
# using masks
data[data.index == "row_name"].T[data.T.index == "col_name"].T
4. Доступ до декількох стовпців:
PYTHON
# by name
data[["col1", "col2", "col3"]]
data.loc[:, ["col1", "col2", "col3"]]
# by index
data.iloc[:, [col1_index, col2_index, col3_index]]
5. Доступ до декількох рядків
PYTHON
# by name
data.loc[["row1", "row2", "row3"]]
# by index
data.iloc[[row1_index, row2_index, row3_index]]
6. Доступ до підмножини, заданої визначеними рядками та стовпцями
PYTHON
# by names
data.loc[["row1", "row2", "row3"], ["col1", "col2", "col3"]]
# by indices
data.iloc[[row1_index, row2_index, row3_index], [col1_index, col2_index, col3_index]]
# column names + row indices
data[["col1", "col2", "col3"]].iloc[[row1_index, row2_index, row3_index]]
# column indices + row names
data.iloc[:, [col1_index, col2_index, col3_index]].loc[["row1", "row2", "row3"]]
7. Доступ до підмножини, заданої діапазонами рядків та стовпців
PYTHON
# by name
data.loc["row1":"row2", "col1":"col2"]
# by index
data.iloc[row1_index:row2_index, col1_index:col2_index]
# column names + row indices
data.loc[:, "col1_name":"col2_name"].iloc[row1_index:row2_index]
# column indices + row names
data.iloc[:, col1_index:col2_index].loc["row1":"row2"]
Серед багатьох варіантів dir() пропонує функцію
median(). Таким чином,