Основи синтаксису Python для науки про дані
Останні кілька днів я читав книгу Data Science from Scrach (PDF-версія), яка є чудовим і зрозумілим вступом до науки про дані. Один із розділів присвячений базовому синтаксису Python та його розширеним можливостям, що часто використовуються в науці про дані. Опис був настільки вдалим, лаконічним і зрозумілим, що я вирішив перекласти його та розмістити тут для зручності.
Часто використовувані синтаксичні конструкції Python у науці про дані (основи) Часто використовувані синтаксичні конструкції Python у науці про дані (просунутий рівень)
Цей розділ зосереджений на представленні базових синтаксичних конструкцій та функцій Python (на основі Python 2.7), які є надзвичайно корисними для обробки даних.
Форматування пробілів
Більшість мов програмування використовують дужки для визначення блоків коду, але Python покладається на відступи (індексацію):
for i in [1, 2, 3, 4, 5]:
print i # перший рядок циклу "for i"
for j in [1, 2, 3, 4, 5]:
print j # перший рядок циклу "for j"
print i + j # останній рядок циклу "for j"
print i # останній рядок циклу "for i"
print "done looping"
Це робить код Python надзвичайно читабельним, але також означає, що ви завжди повинні приділяти увагу форматуванню. Пробіли всередині дужок ігноруються, що корисно при написанні довгих виразів:
long_winded_computation = (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20)
Це також покращує читабельність коду:
list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
easier_to_read_list_of_lists = [ [1, 2, 3],
[4 ,5 ,6 ],
[7 ,8 ,9 ] ]
Багаторядкові інструкції
Можна використовувати зворотний слеш для розділення інструкції на кілька рядків (такий підхід використовується рідко):
two_plus_three = 2 + \
3
Модулі
Незалежно від того, чи це вбудовані модулі Python, чи завантажені вами сторонні, їх потрібно імпортувати вручну, перш ніж використовувати.
- Простий імпорт всього модуля напряму:
import re
my_regex = re.compile("[0-9]+", re.I)
Модуль _re_ імпортовано для роботи з регулярними виразами. Після імпорту ви можете викликати його функції, використовуючи назву модуля як префікс (наприклад, re.).
- Якщо ім’я модуля, який ви хочете імпортувати, вже використовується у вашому коді, ви можете імпортувати його під іншим псевдонімом:
import re as regex
my_regex = regex.compile("[0-9]+", regex.I)
- Якщо ви “поганий” програміст, ви можете імпортувати весь модуль у поточний простір імен. Це може випадково перезаписати вже визначені вами змінні:
match = 10
from re import * # у модулі re є функція match
print match # виводить функцію match
Оскільки ви хороша людина, я впевнений, що ви так не робитимете.
Арифметичні операції
Python 2.7 за замовчуванням виконує цілочисельне ділення, тому $ 5 / 2 = 2 $. Однак часто нам потрібен не цілий результат, тому можна імпортувати цей модуль:
from __future__ import division
Після імпорту ви отримаєте $5 / 2 = 2.5$. Цілочисельне ділення: $5 // 2 = 2$.
Функції
Визначення функцій
Функція — це правило, яке може приймати нуль або більше вхідних значень і повертати певний результат. У Python ми визначаємо функцію за допомогою def ім'я_функції(параметри):
def double(x):
"""Тут можна написати пояснення щодо призначення функції.
Наприклад, ця функція множить вхідне значення на 2"""
# Тут розміщується тіло функції, не забудьте про відступи.
return x * 2
Використання функцій
У Python функції є об’єктами “першого класу”. Це означає, що ми можемо присвоювати їх змінним або передавати як аргументи іншим функціям:
def apply_to_one(f):
"""Викликає функцію f, передаючи 1 як її аргумент"""
return f(1)
my_double = double # double посилається на функцію, визначену в попередньому розділі
x = apply_to_one(my_double) # x дорівнює 2
Анонімні функції
Також можна створювати анонімні функції за допомогою lambda:
y = apply_to_one(lambda x: x + 4) # дорівнює 5
Хоча lambda можна присвоїти змінній, більшість програмістів рекомендують використовувати _def_:
another_double = lambda x: 2 * x # не рекомендовано
def another_double(x): return 2 * x # рекомендовано
Додатково:
lambda— це лише вираз, а тіло функції значно простіше, ніж уdef.- Тіло
lambdaє виразом, а не блоком коду. У виразlambdaможна інкапсулювати лише обмежену логіку.
Передача параметрів функції
Параметри функції можуть мати значення за замовчуванням. Якщо аргумент не передано, буде використано значення за замовчуванням; якщо передано — використається вказане значення:
def my_print(message="my default message"):
print message
my_print("hello") # виводить "hello"
my_print() # виводить "my default message"
Іноді зручно вказувати аргументи за їхніми іменами:
def subtract(a=0, b=0):
return a - b
subtract(10, 5) # повертає 5
subtract(0, 5) # повертає -5
subtract(b=5) # те саме, що й попереднє, повертає -5
Рядки
Для створення рядків можна використовувати одинарні або подвійні лапки (лапки завжди повинні бути парними):
single_quoted_string = 'data science'
double_quoted_string = "data science"
Зворотний слеш використовується для екранування символів, наприклад:
tab_string = "\t" # означає символ табуляції
len(tab_string) # дорівнює 1
Якщо ви хочете використовувати сам зворотний слеш (наприклад, для шляхів у Windows або регулярних виразів), його можна визначити за допомогою сирих рядків r"":
not_tab_string = r"\t" # означає символи '\' та 't'
len(not_tab_string) # дорівнює 2
Для створення багаторядкових рядків використовуйте потрійні подвійні лапки:
multi_line_string = """Це перший рядок
Це другий рядок
Це третій рядок"""
Обробка винятків
Коли програма стикається з помилкою, Python генерує виняток (exception). Якщо ми не обробимо його, програма завершить свою роботу. Обробляти винятки можна за допомогою операторів try та except:
try:
print 0 / 0
except ZeroDivisionError:
print "не можна ділити на 0"
Хоча в інших мовах винятки часто розглядаються як небажані явища, у Python обробка винятків може зробити ваш код більш лаконічним і чистим.
Списки
Створення списків
Списки — це прості впорядковані колекції, що є однією з найфундаментальніших структур даних у Python (схожі на масиви в інших мовах, але мають додаткові можливості). Створити список можна так:
integer_list = [1, 2, 3]
heterogeneous_list = ["string", 0.1, True]
list_of_lists = [ integer_list, heterogeneous_list, [] ]
list_length = len(integer_list) # дорівнює 3
list_sum = sum(integer_list) # дорівнює 6
Доступ до елементів списку
Ви можете отримати доступ до значень у списку за допомогою індексів у квадратних дужках:
x = range(10) # x отримує список x = [0, 1, ..., 9]
zero = x[0] # дорівнює 0, нумерація елементів списку починається з 0
one = x[1] # дорівнює 1
nine = x[-1] # дорівнює 9, останній елемент списку
eight = x[-2] # дорівнює 8, передостанній елемент списку
x[0] = -1 # поточний список x = [-1, 1, 2, 3, ..., 9]
Зрізи списків
Списки можна “нарізати” за допомогою квадратних дужок:
first_three = x[:3] # [-1, 1, 2]
three_to_end = x[3:] # [3, 4, ..., 9]
one_to_four = x[1:5] # [1, 2, 3, 4]
last_three = x[-3:] # [7, 8, 9]
without_first_and_last = x[1:-1] # [1, 2, ..., 8]
copy_of_x = x[:] # [-1, 1, 2, ..., 9]
За допомогою in можна перевірити, чи міститься певний елемент у списку:
1 in [1, 2, 3] # True
0 in [1, 2, 3] # False
Цей спосіб пошуку елементів є неефективним і слід використовувати лише для дуже малих списків або коли швидкість пошуку не має значення.
Об’єднання списків
У Python дуже легко об’єднувати два списки:
x = [1, 2, 3]
x.extend([4, 5, 6]) # поточний x = [1,2,3,4,5,6]
Якщо ви не хочете змінювати оригінальний список x, можете створити новий список за допомогою оператора додавання:
x = [1, 2, 3]
y = x + [4, 5, 6] # поточний y = [1, 2, 3, 4, 5, 6]; x не змінився
Часто елементи додають у список по одному таким чином:
x = [1, 2, 3]
x.append(0) # поточний x = [1, 2, 3, 0]
y = x[-1] # дорівнює 0
z = len(x) # дорівнює 4
Розпакування списків
Якщо ви знаєте кількість елементів у списку, ви можете легко його розпакувати:
x, y = [1, 2] # поточний x = 1, y = 2
Якщо кількість елементів з обох сторін присвоєння не збігається, ви отримаєте помилку ValueError. Тому частіше використовують підкреслення для ігнорування решти елементів списку:
_, y = [1, 2] # поточний y == 2, перший елемент ігнорується
Кортежі
Списки та кортежі дуже схожі. Єдина відмінність від списків полягає в тому, що елементи кортежу не можуть бути змінені.
Створення кортежів
Кортежі можна створювати за допомогою круглих дужок або взагалі без них:
my_tuple = (1, 2)
other_tuple = 3, 4
my_list[1] = 3 # поточний my_list = [1, 3]
try:
my_tuple[1] = 3
except TypeError:
print "неможливо змінити кортеж"
Кортежі дуже зручні для повернення кількох значень з функції:
def sum_and_product(x, y):
return (x + y),(x * y)
sp = sum_and_product(2, 3) # дорівнює (5, 6)
s, p = sum_and_product(5, 10) # s = 15, p = 50
Кортежі (і списки) підтримують одночасне присвоєння кількох елементів:
x, y = 1, 2 # поточний x = 1, y = 2
x, y = y, x # обмін значень двох змінних у Python; поточний x = 2, y = 1
Словники
Створення словників
Ще одна фундаментальна структура даних у Python — це словник. Він дозволяє швидко отримувати значення (value) за їхнім ключем (key):
empty_dict = {} # дуже пітонічний спосіб визначення порожнього словника
empty_dict2 = dict() # менш пітонічний спосіб визначення порожнього словника
grades = { "Joel" : 80, "Tim" : 95 } # зберігання словника
Пошук елементів у словнику
Ви можете знайти відповідне значення, використовуючи квадратні дужки та ключ:
joels_grade = grades["Joel"] # дорівнює 80
Якщо ключ, який ви шукаєте, відсутній у словнику, буде повернуто KeyError:
try:
kates_grade = grades["Kate"]
except KeyError:
print "немає оцінки для Кейт!"
За допомогою in можна перевірити, чи існує ключ у словнику:
joel_has_grade = "Joel" in grades # True
kate_has_grade = "Kate" in grades # False
Словники мають метод, який дозволяє повертати значення за замовчуванням, якщо шуканий ключ відсутній (замість генерації винятку):
joels_grade = grades.get("Joel", 0) # дорівнює 80
kates_grade = grades.get("Kate", 0) # дорівнює 0
no_ones_grade = grades.get("No One") # повертає значення за замовчуванням None
Модифікація словників
Пари ключ-значення у словнику можна створювати та змінювати за допомогою квадратних дужок:
grades["Tim"] = 99 # замінює старе значення
grades["Kate"] = 100 # додає пару ключ-значення
num_students = len(grades) # дорівнює 3
Ми часто використовуватимемо словники для представлення структур даних, наприклад так:
tweet = {
"user" : "joelgrus",
"text" : "Data Science is Awesome",
"retweet_count" : 100,
"hashtags" : ["#data", "#science", "#datascience", "#awesome", "#yolo"]
}
Окрім пошуку за конкретним ключем, ми також можемо працювати з усіма ключами наступним чином:
tweet_keys = tweet.keys() # отримує список ключів
tweet_values = tweet.values() # отримує список значень
tweet_items = tweet.items() # отримує кортежі (ключ, значення)
"user" in tweet_keys # повертає True, використовується менш ефективний пошук in у списку
"user" in tweet # більш пітонічний спосіб, використовується ефективний пошук in у словнику
"joelgrus" in tweet_values # True
Ключі у словниках є унікальними, і списки не можуть використовуватися як ключі словника. Якщо вам потрібен складений ключ, ви можете використовувати кортежі або перетворити ключ на рядок.
Словники за замовчуванням
Якщо ви намагаєтеся підрахувати частоту кожного слова в документі, очевидний підхід полягає у створенні словника, де слова є ключами, а їхні частоти — значеннями. Потім ви перебираєте документ, збільшуючи значення для вже існуючих слів або додаючи нову пару ключ-значення для нових слів:
word_counts = {}
for word in document:
if word in word_counts:
word_counts[word] += 1
else:
word_counts[word] = 1
Звичайно, ви також можете використовувати підхід “спершу дія, потім перевірка” для обробки відсутнього ключа, наприклад так:
word_counts = {}
for word in document:
try:
word_counts[word] += 1
except KeyError:
word_counts[word] = 1
Третій спосіб — використовувати метод get, який чудово справляється з обробкою відсутніх ключів:
word_counts = {}
for word in document:
previous_count = word_counts.get(word, 0)
word_counts[word] = previous_count + 1
Словники за замовчуванням (defaultdict) працюють так само, як і звичайні словники, з однією відмінністю: коли ви намагаєтеся отримати доступ до неіснуючого ключа, defaultdict автоматично створює пару ключ-значення, використовуючи наданий вами тип. Щоб використовувати defaultdict, вам потрібно імпортувати бібліотеку collections:
from collections import defaultdict
word_counts = defaultdict(int) # int() генерує 0
for word in document:
word_counts[word] += 1
Defaultdict також дуже корисний зі списками, звичайними словниками і навіть власними функціями:
dd_list = defaultdict(list) # list() генерує порожній список
dd_list[2].append(1) # поточний dd_list = {2: [1]}
dd_dict = defaultdict(dict) # dict() генерує порожній словник
dd_dict["Joel"]["City"] = "Seattle" # поточний dd_dict містить { "Joel" : { "City" : "Seattle"}}
dd_pair = defaultdict(lambda: [0, 0]) # створює словник, де значеннями є списки
dd_pair[2][1] = 1 # поточний dd_pair містить {2: [0,1]}
Цей підхід дуже корисний, оскільки в майбутньому, коли ми будемо отримувати значення за певними ключами зі словника, нам більше не потрібно буде перевіряти їхнє існування.
Лічильники Counter
Лічильник (Counter) може безпосередньо перетворити набір значень на об’єкт, схожий на словник, де ключем є елемент набору, а значенням — кількість його появ. Це часто використовується при створенні гістограм:
from collections import Counter
c = Counter([0, 1, 2, 0]) # c (приблизно) дорівнює { 0 : 2, 1 : 1, 2 : 1 }
Таким чином, ми отримуємо дуже зручний спосіб підрахунку частоти слів:
word_counts = Counter(document)
Лічильник також має дуже корисний метод most_common, який дозволяє безпосередньо отримати кілька найчастіше вживаних слів та їхні частоти:
# виводить 10 найчастіше вживаних слів та їхні лічильники
for word, count in word_counts.most_common(10):
print word, count
Множини
Ще одна структура даних у Python — це множина (set). Множина — це невпорядкована колекція унікальних елементів. Створити множину та додати до неї елементи можна так:
s = set()
s.add(1) # s дорівнює { 1 }
s.add(2) # s дорівнює { 1, 2 }
s.add(2) # s дорівнює { 1, 2 }
x = len(s) # дорівнює 2
y = 2 in s # дорівнює True
z = 3 in s # дорівнює False
Дві головні причини використовувати множини:
По-перше, операція in у множинах надзвичайно ефективна. Коли кількість елементів у наборі даних дуже велика, пошук елементів у вигляді множини значно кращий, ніж у списку:
stopwords_list = ["a","an","at"] + hundreds_of_other_words + ["yet", "you"]
"zip" in stopwords_list # неефективно, вимагає перевірки кожного елемента
stopwords_set = set(stopwords_list)
"zip" in stopwords_set # пошук успішний і дуже швидкий
По-друге, множини дуже зручні для отримання унікальних елементів з набору даних:
item_list = [1, 2, 3, 1, 2, 3]
num_items = len(item_list) # 6
item_set = set(item_list) # {1, 2, 3}
num_distinct_items = len(item_set) # 3
distinct_item_list = list(item_set) # [1, 2, 3]
Однак на практиці множини використовуються не так часто, як словники та списки.
Умовні оператори
У більшості мов програмування ви можете використовувати _if_ для вираження умовних розгалужень, наприклад так:
if 1 > 2:
message = "якби тільки 1 був більшим за 2…"
elif 1 > 3:
message = "elif означає 'else if'"
else:
message = "коли все інше не спрацьовує, використовуйте else (якщо хочете)"
Ви також можете написати умовний оператор в одному рядку, але це використовується рідко:
parity = "even" if x % 2 == 0 else "odd"
Цикли
Цикл _while_
Цикл while у Python:
x = 0
while x < 10:
print x, "менше 10"
x += 1
Цикл _for_
Частіше використовується цикл for-in:
for x in range(10):
print x, "менше 10"
Для складніших логічних виразів можна використовувати оператори continue та break:
for x in range(10):
if x == 3:
continue # переходить до наступної ітерації циклу
if x == 5:
break # повністю виходить з циклу
print x
У результаті буде виведено 0, 1, 2 та 4.
Правдивість
Логічні змінні Booleans у Python використовуються приблизно так само, як і в інших мовах, єдина відмінність — перша літера завжди має бути великою:
one_is_less_than_two = 1 < 2 # дорівнює True
true_equals_false = True == False # дорівнює False
Python використовує None для позначення відсутності значення, подібно до null в інших мовах:
x = None
print x == None # виводить True, не дуже елегантно
print x is None # виводить True, елегантніше
Python дозволяє використовувати інші значення замість булевих. Наведені нижче є еквівалентними False:
- False
- None
- [] (порожній список)
- {} (порожній словник)
- “”
- set()
- 0
- 0.0
Аналогічно існує багато значень, еквівалентних True. Це дозволяє дуже зручно перевіряти порожні списки, рядки, словники тощо.
Звісно, якщо ви не можете передбачити результат, це може призвести до помилок під час використання:
s = some_function_that_returns_a_string()
if s:
first_char = s[0]
else:
first_char = ""
Простіший підхід, який дає той самий результат, що й попередній:
first_char = s and s[0]
Якщо перше значення є істинним, повертається друге значення; в іншому випадку — перше.
Аналогічно, якщо x може бути числом або None, то таким чином можна отримати x, яке гарантовано буде числом:
safe_x = x or 0
У Python також є функція all, яка повертає True, якщо всі елементи є істинними. Функція any повертає True, якщо хоча б один елемент є істинним. Наприклад, для списку, де кожен елемент є “істинним”, функція all поверне True, інакше — False:
all([True, 1, { 3 }]) # True
all([True, 1, {}]) # False, {} еквівалентно "False"
any([True, 1, {}]) # True
all([]) # True, немає елемента, еквівалентного "False"
any([]) # False, немає елемента, еквівалентного "True"
Додатково: Часто використовувані синтаксичні конструкції Python у науці про дані (просунутий рівень)