Често използван синтаксис на Python в Data Science (Основи)

Напоследък чета книгата Data Science from Scratch (PDF адрес), която е отлична и лесно разбираема въвеждаща книга за data science. Една от главите представя основния синтаксис на Python и по-напреднал синтаксис, често използван в data science. Намирам представянето за много добро, кратко и ясно. Затова реших да го преведа и да го публикувам тук за справка. Често използван синтаксис на Python в Data Science (Основи) Често използван синтаксис на Python в Data Science (Напреднало ниво)

Тази глава се фокусира върху основните функции и синтаксис на 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  

Модули (Modules)

Независимо дали става въпрос за вградени в Python модули или за такива, които сте изтеглили от трети страни, те трябва да бъдат импортирани ръчно, за да могат да се използват.

  1. Просто импортирайте целия модул директно:
import re  
my_regex = re.compile("[0-9]+", re.I)  

Модулът _re_, импортиран тук, се използва за регулярни изрази. След импортирането на модула можете директно да извиквате специфични функции, като използвате името на модула като префикс (re.).

  1. Ако името на модула, който импортирате, вече се използва в кода, можете да го импортирате под друго име:
import re as regex  
my_regex = regex.compile("[0-9]+", regex.I)  
  1. Ако сте по-малко предпазливи, можете да импортирате целия модул в текущото пространство от имена, което може неволно да презапише вече дефинирани от вас променливи:
match = 10  
from re import *  # Модулът re има функция match
print match       # Извежда функцията match

Но тъй като сте добросъвестни, предполагам, че няма да правите това.

Аритметични операции (Arithmetic)

По подразбиране Python 2.7 използва целочислено деление, така че $5 / 2 = 2$. Често обаче не искаме подобно деление. В такива случаи можем да импортираме следния модул:

from __future__ import division  

След импортирането ще имаме $5 / 2 = 2.5$. Целочислено деление: $5 // 2 = 2$.

Функции (Functions)

Дефиниране на функции

Функциите са правила, които приемат нула или повече входа и връщат определен изход. В 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   # Препоръчителна практика

Допълнително:

Предаване на аргументи на функции

Параметрите на функциите могат да имат стойности по подразбиране. Ако не подадете аргумент за такъв параметър, ще се използва стойността по подразбиране; ако подадете, ще се използва подадената стойност:

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

Низове (Strings)

Можете да използвате единични или двойни кавички за създаване на низове (кавичките трябва да са съвпадащи):

single_quoted_string = 'data science'  
double_quoted_string = "data science"  

Използваме обратна наклонена черта за escape символи, например:

tab_string = "\t"      # Представлява табулатор
len(tab_string)        # Равно на 1

Когато искате да използвате самата обратна наклонена черта (например за пътища на Windows или регулярни изрази), можете да дефинирате raw string с r"":

not_tab_string = r"\t" # Представлява символите '\' и 't'
len(not_tab_string)    # Равно на 2

Използвайте тройни двойни кавички, за да създадете многоредови низове:

multi_line_string = """Това е първи ред
Това е втори ред
Това е трети ред"""

Обработка на изключения (Exception)

Когато възникне грешка в програмата, Python генерира изключение (exception). Ако не го обработим, програмата ще прекъсне изпълнението си. Можем да прихващаме изключения с конструкциите try и except:

try:  
    print 0 / 0  
except ZeroDivisionError:  
    print "Не може да се дели на 0"

Въпреки че в други езици изключенията често се разглеждат като нежелани явления, в Python тяхната обработка може да направи кода ви по-чист и елегантен.

Списъци (Lists)

Създаване на списъци

Списъците са прости, подредени колекции и са една от най-основните структури от данни в 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 = [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]

Извличане на части от списък (Slicing)

Можете да извличате части от списък (slicing) с квадратни скоби:

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, без значение за първия елемент

Кортежи (Tuples)

Списъците и кортежите са много сходни. Единствената разлика е, че елементите в кортеж не могат да бъдат променяни.

Създаване на кортежи

Можете да създавате кортежи с кръгли скоби или без никакви скоби:

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

Речници (Dictionaries)

Създаване на речници

Друга основна структура от данни в Python е речникът, който ви позволява бързо да достъпвате стойности чрез техния ключ:

empty_dict = {}                       # Много Python-ски дефиниция на празен речник
empty_dict2 = dict()                  # Не толкова Python-ски дефиниция на празен речник
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                       # По-Python-ски начин, използва ефективно in търсене в речник
"joelgrus" in tweet_values            # True

Ключовете в речника са уникални и списъците не могат да се използват като ключове в речник. Ако имате нужда от многокомпонентен ключ, можете да използвате кортеж или да конвертирате ключа в низ по някакъв начин.

Фабрични речници (defaultdict)

Ако се опитвате да преброите честотата на всяка дума в документ, очевидният подход е да създадете речник, където думите са ключове, а честотите са съответните стойности. След това обхождате документа, и когато срещнете вече съществуваща дума, увеличавате стойността на съответния ключ с 1; когато срещнете нова дума, добавяте двойка ключ-стойност към речника:

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, трябва да импортирате библиотеката 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  

Множества (Sets)

Друга структура от данни в 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.

Булева стойност (Truthiness)

Булевите променливи в 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:

По подобен начин има много еквиваленти на 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, ако всички елементи са True. Функцията any връща True, ако поне един елемент е 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 в Data Science (напреднало ниво)