Sintaxă Python comună în știința datelor (Noțiuni de bază)

Zilele acestea am parcurs cartea Data Science from Scrach (adresa PDF), o introducere excelentă și accesibilă în știința datelor. Unul dintre capitole prezintă sintaxa Python de bază și cea avansată, frecvent utilizată în știința datelor. Mi s-a părut o prezentare foarte bună, concisă și clară, așa că am tradus-o aici ca notițe pentru referințe viitoare. Sintaxa Python frecvent utilizată în știința datelor (Noțiuni de bază) Sintaxa Python frecvent utilizată în știința datelor (Nivel avansat)

Acest capitol se concentrează pe prezentarea sintaxei și funcționalităților de bază ale Python, extrem de utile în procesarea datelor (bazat pe Python 2.7).

Formatarea prin spațiere

Multe limbaje de programare folosesc acolade pentru a controla blocurile de cod, însă Python se bazează pe indentare:

for i in [1, 2, 3, 4, 5]:  
    print i          # Prima linie a buclei "for i"  
    for j in [1, 2, 3, 4, 5]:  
        print j      # Prima linie a buclei "for j"  
        print i + j  # Ultima linie a buclei "for j"  
    print i          # Ultima linie a buclei "for i"  
print "done looping"  

Acest lucru face codul Python foarte ușor de citit, dar implică și o atenție constantă la formatare. Spațiile din interiorul parantezelor sunt ignorate, ceea ce este util atunci când scrii expresii lungi:

long_winded_computation = (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20)  

De asemenea, facilitează citirea codului:

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 ] ]  

Instrucțiuni pe mai multe rânduri

Poți folosi un backslash pentru a indica o instrucțiune întreruptă pe două rânduri (o practică rar întâlnită):

two_plus_three = 2 + \
                 3  

Module

Indiferent dacă sunt module încorporate în Python sau module terțe descărcate, toate trebuie importate manual pentru a putea fi utilizate.

  1. Importă pur și simplu întregul modul:
import re  
my_regex = re.compile("[0-9]+", re.I)  

Modulul _re_ importat aici este folosit pentru expresii regulate. După importarea modulului, poți apela funcționalitățile specifice utilizând numele modulului ca prefix (re.).

  1. Dacă numele modulului pe care vrei să-l importezi este deja utilizat în cod, îl poți mapa la un alt nume în timpul importului:
import re as regex  
my_regex = regex.compile("[0-9]+", regex.I)  
  1. Dacă ești un tip mai “neastâmpărat”, poți importa întregul modul în spațiul de nume curent, ceea ce ar putea suprascrie, fără intenție, variabilele pe care le-ai definit deja:
match = 10  
from re import *  # Modulul re are o funcție match  
print match       # Afișează funcția match  

Dar, pentru că ești o persoană bună, sunt sigur că nu vei face asta.

Operații aritmetice

Python 2.7 folosește implicit împărțirea întreagă, deci $5 / 2 = 2$. Însă, de multe ori nu ne dorim acest comportament, așa că putem importa acest modul:

from __future__ import division  

După import, vom avea $5 / 2 = 2.5$. Împărțirea întreagă (floor division): $5 // 2 = 2$.

Funcții

Definirea funcțiilor

O funcție este o regulă care poate primi zero sau mai multe intrări și returna o anumită ieșire. În Python, definim o funcție folosind def nume_funcție(parametri):

def double(x):  
    """Aici poți scrie o explicație despre funcționalitatea funcției,  
    de exemplu, că această funcție înmulțește intrarea cu 2"""  
    # Aici poți scrie corpul funcției, nu uita de indentare  
    return x * 2  

Utilizarea funcțiilor

În Python, funcțiile sunt “cetățeni de primă clasă”, ceea ce înseamnă că le putem atribui unei variabile sau le putem transmite ca argumente altor funcții:

def apply_to_one(f):  
    """Apelează funcția f și transmite 1 ca parametru al funcției"""  
    return f(1)  
my_double = double          # double se referă la funcția definită în secțiunea anterioară  
x = apply_to_one(my_double) # x este egal cu 2  

Funcții anonime

De asemenea, poți crea funcții anonime folosind lambda:

y = apply_to_one(lambda x: x + 4)     # este egal cu 5  

Poți atribui o funcție lambda unei variabile, dar majoritatea programatorilor te vor sfătui să folosești pe cât posibil def:

another_double = lambda x: 2 * x      # Nu este recomandat  
def another_double(x): return 2 * x   # Metoda recomandată  

Observații suplimentare:

Transmiterea parametrilor funcțiilor

Parametrii funcțiilor pot avea valori implicite. Dacă o funcție este apelată fără a specifica un parametru, se va folosi valoarea implicită; dacă un parametru este specificat, acea valoare va fi transmisă:

def my_print(message="my default message"):  
    print message  
my_print("hello")     # Afișează "hello"  
my_print()            # Afișează "my default message"  

Uneori este foarte util să specifici parametrii direct prin numele lor:

def subtract(a=0, b=0):  
    return a - b  
subtract(10, 5)   # Returnează 5  
subtract(0, 5)    # Returnează -5  
subtract(b=5)     # La fel ca precedentul, returnează -5  

Șiruri de caractere (Strings)

Poți folosi ghilimele simple sau duble pentru a crea șiruri de caractere (ghilimelele trebuie să fie pereche):

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

Folosește backslash-ul pentru caracterele escape, de exemplu:

tab_string = "\t"      # Reprezintă un tab  
len(tab_string)        # Este egal cu 1  

Când vrei să utilizezi backslash-ul în sine (pentru directoarele Windows sau expresii regulate), îl poți defini folosind șiruri brute r"":

not_tab_string = r"\t" # Reprezintă caracterele '\' și 't'  
len(not_tab_string)    # Este egal cu 2  

Creează șiruri de caractere pe mai multe rânduri folosind trei ghilimele duble:

multi_line_string = """Aceasta este prima linie  
Aceasta este a doua linie  
Aceasta este a treia linie"""  

Gestionarea excepțiilor

Când apare o eroare în program, Python generează o excepție. Dacă nu o gestionăm, programul se va opri din execuție. Poți captura excepțiile folosind instrucțiunile try și except:

try:  
    print 0 / 0  
except ZeroDivisionError:  
    print "Cannot divide by 0"  

Deși în alte limbaje excepțiile sunt adesea văzute ca un lucru negativ, în Python, gestionarea excepțiilor multiple poate face codul tău mai concis și mai curat.

Liste

Crearea listelor

Listele sunt colecții simple, ordonate, și sunt una dintre cele mai fundamentale structuri de date din Python (asemănătoare cu array-urile din alte limbaje, dar cu anumite caracteristici suplimentare). Pentru a crea o listă:

integer_list = [1, 2, 3]  
heterogeneous_list = ["string", 0.1, True]  
list_of_lists = [ integer_list, heterogeneous_list, [] ]  
list_length = len(integer_list)   # Este egal cu 3  
list_sum = sum(integer_list)      # Este egal cu 6  

Accesarea valorilor din liste

Poți accesa valorile dintr-o listă prin indexare cu paranteze pătrate:

x = range(10)       # Lista x devine [0, 1, ..., 9]  
zero = x[0]         # Este egal cu 0, indexarea listelor începe de la 0  
one = x[1]          # Este egal cu 1  
nine = x[-1]        # Este egal cu 9, ultimul element din listă  
eight = x[-2]       # Este egal cu 8, al doilea element de la sfârșitul listei  
x[0] = -1           # Lista curentă x = [-1, 1, 2, 3, ..., 9]  

Trunchierea listelor (slicing)

Poți trunchia liste folosind paranteze pătrate:

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]  

Poți folosi operatorul in pentru a verifica dacă un element se află într-o listă:

1 in [1, 2, 3]        # True  
0 in [1, 2, 3]        # False  

Această metodă de căutare a elementelor este foarte ineficientă; ar trebui să o folosești doar când lista este foarte mică sau dacă timpul de căutare nu este o preocupare.

Concatenarea listelor

În Python, este foarte ușor să concatenezi două liste:

x = [1, 2, 3]  
x.extend([4, 5, 6])   # x este acum [1,2,3,4,5,6]  

Dacă nu vrei să modifici lista originală x, poți folosi operatorul de “adunare” pentru a crea o nouă listă:

x = [1, 2, 3]  
y = x + [4, 5, 6]     # y este acum [1, 2, 3, 4, 5, 6]; x nu s-a modificat  

Poți adăuga frecvent elemente în listă câte unul, în felul următor:

x = [1, 2, 3]  
x.append(0)           # x este acum [1, 2, 3, 0]  
y = x[-1]             # Este egal cu 0  
z = len(x)            # Este egal cu 4  

Descompunerea listelor (unpacking)

Dacă știi câte elemente are o listă, o poți descompune foarte ușor:

x, y = [1, 2]         # x este acum 1, y este 2  

Dacă numărul de elemente de pe cele două părți ale egalității nu corespunde, vei primi o eroare de valoare (ValueError). De aceea, mai frecvent, folosim un underscore pentru a stoca restul listei:

_, y = [1, 2]         # y == 2, indiferent de primul element  

Tupluri

Listele și tuplurile sunt foarte asemănătoare, singura diferență fiind că elementele dintr-un tuplu nu pot fi modificate.

Crearea tuplurilor

Poți crea tupluri folosind paranteze rotunde sau fără paranteze:

my_tuple = (1, 2)  
other_tuple = 3, 4  
my_list = [1, 2]
my_list[1] = 3        # my_list este acum [1, 3]  
try:  
    my_tuple[1] = 3  
except TypeError:  
    print "Cannot modify tuple"  

Tuplurile sunt foarte utile pentru a obține mai multe valori de retur de la o funcție:

def sum_and_product(x, y):  
    return (x + y),(x * y)  
sp = sum_and_product(2, 3)    # Este egal cu (5, 6)  
s, p = sum_and_product(5, 10) # s = 15, p = 50  

Atât tuplurile (cât și listele) suportă atribuirea simultană a mai multor elemente:

x, y = 1, 2       # x este acum 1, y este 2  
x, y = y, x       # Schimbă valorile a două variabile în Python; x este acum 2, y este 1  

Dicționare

Crearea dicționarelor

O altă structură de date fundamentală în Python este dicționarul, care îți permite să obții rapid o valoare (value) corespunzătoare unei chei (key):

empty_dict = {}                       # Definirea unui dicționar gol, într-un stil foarte "pythonic"  
empty_dict2 = dict()                  # Definirea unui dicționar gol, mai puțin "pythonic"  
grades = { "Joel" : 80, "Tim" : 95 }  # Stocare în dicționar  

Căutarea elementelor în dicționare

Poți folosi paranteze pătrate cu cheia pentru a căuta valoarea corespunzătoare:

joels_grade = grades["Joel"]          # Este egal cu 80  

Dacă cheia căutată nu se află în dicționar, se va returna o eroare de cheie (KeyError):

try:  
    kates_grade = grades["Kate"]  
except KeyError:  
    print "no grade for Kate!"  

Poți verifica dacă o cheie se află în dicționar folosind operatorul in:

joel_has_grade = "Joel" in grades     # True  
kate_has_grade = "Kate" in grades     # False  

Dicționarele au o metodă care poate returna o valoare implicită, astfel încât, dacă cheia căutată nu se află în dicționar, se va returna valoarea implicită stabilită (în loc să genereze o excepție):

joels_grade = grades.get("Joel", 0)   # Este egal cu 80  
kates_grade = grades.get("Kate", 0)   # Este egal cu 0  
no_ones_grade = grades.get("No One")  # Returnează valoarea implicită None  

Modificarea dicționarilor

Poți folosi paranteze pătrate pentru a crea sau modifica perechi cheie-valoare în dicționar:

grades["Tim"] = 99                    # Înlocuiește valoarea veche  
grades["Kate"] = 100                  # Adaugă o pereche cheie-valoare nouă  
num_students = len(grades)            # Este egal cu 3  

Vom folosi frecvent dicționarele în acest mod pentru a reprezenta structura datelor:

tweet = {  
    "user" : "joelgrus",  
    "text" : "Data Science is Awesome",  
    "retweet_count" : 100,  
    "hashtags" : ["#data", "#science", "#datascience", "#awesome", "#yolo"]  
}  

Pe lângă căutarea unor chei specifice, putem opera și cu toate cheile în felul următor:

tweet_keys = tweet.keys()             # Obține o listă de chei  
tweet_values = tweet.values()         # Obține o listă de valori  
tweet_items = tweet.items()           # Obține tupluri (cheie, valoare)  
"user" in tweet_keys                  # Returnează True, folosind o căutare 'in' mai puțin eficientă, specifică listelor  
"user" in tweet                       # O metodă mai "pythonică", folosind o căutare 'in' eficientă, specifică dicționarelor  
"joelgrus" in tweet_values            # True  

Cheile din dicționare sunt unice, iar listele nu pot fi folosite ca chei de dicționar. Dacă ai nevoie de o cheie formată din mai multe părți, poți utiliza un tuplu sau poți converti cheia într-un șir de caractere.

Defaultdict

Dacă încerci să numeri frecvența fiecărui cuvânt dintr-un document, o abordare evidentă este să creezi un dicționar în care cuvântul este cheia, iar frecvența este valoarea corespunzătoare. Apoi parcurgi documentul, iar când întâlnești un cuvânt existent, incrementezi valoarea asociată în dicționar; când întâlnești un cuvânt nou, adaugi o nouă pereche cheie-valoare în dicționar:

word_counts = {}  
for word in document:  
    if word in word_counts:  
        word_counts[word] += 1  
    else:  
        word_counts[word] = 1  

Desigur, poți trata o cheie lipsă în avans, într-un mod “întâi acționezi, apoi explici”, ca acesta:

word_counts = {}  
for word in document:  
    try:  
        word_counts[word] += 1  
    except KeyError:  
        word_counts[word] = 1  

A treia metodă este utilizarea get, care gestionează excelent cheile lipsă:

word_counts = {}  
for word in document:  
    previous_count = word_counts.get(word, 0)  
    word_counts[word] = previous_count + 1  

Defaultdict-ul este similar cu un dicționar obișnuit, cu singura diferență că, atunci când încerci să accesezi o cheie inexistentă, defaultdict-ul va crea automat o pereche cheie-valoare folosind cheia furnizată. Pentru a utiliza defaultdict, trebuie să imporți biblioteca collections:

from collections import defaultdict  
word_counts = defaultdict(int)        # int() generează 0  
for word in document:  
    word_counts[word] += 1  

Defaultdict-urile sunt, de asemenea, foarte utile cu liste, dicționare obișnuite sau chiar funcții personalizate:

dd_list = defaultdict(list)           # list() generează o listă goală  
dd_list[2].append(1)                  # dd_list este acum {2: [1]}  
dd_dict = defaultdict(dict)           # dict() generează un dicționar gol  
dd_dict["Joel"]["City"] = "Seattle"   # Conținutul curent al dd_dict este { "Joel" : { "City" : "Seattle"}}  
dd_pair = defaultdict(lambda: [0, 0]) # Creează un dicționar unde valorile asociate cheilor sunt liste  
dd_pair[2][1] = 1                     # Conținutul curent al dd_pair este {2: [0,1]}  

Această metodă este foarte utilă, eliminând necesitatea de a verifica existența unei chei înainte de a accesa valorile asociate în dicționar.

Contoare (Counter)

Un Counter poate converti direct un set de valori într-un obiect similar cu un dicționar, unde cheia este un element din set, iar valoarea corespunzătoare este numărul de apariții al acelui element. Acest lucru este frecvent utilizat la crearea histogramelor:

from collections import Counter  
c = Counter([0, 1, 2, 0]) # c este (aproximativ) { 0 : 2, 1 : 1, 2 : 1 }  

Astfel, avem o metodă foarte convenabilă pentru a număra frecvența cuvintelor:

word_counts = Counter(document)  

Counter are și o metodă foarte utilă, most_common, care poate returna direct cele mai frecvente cuvinte și frecvențele lor corespunzătoare:

# Afișează primele 10 cele mai frecvente cuvinte și numărul lor de apariții  
for word, count in word_counts.most_common(10):  
    print word, count  

Seturi

O altă structură de date în Python sunt seturile, care reprezintă o colecție de elemente distincte. Poți crea un set și adăuga elemente în el astfel:

s = set()  
s.add(1)          # s este { 1 }  
s.add(2)          # s este { 1, 2 }  
s.add(2)          # s este { 1, 2 }  
x = len(s)        # Este egal cu 2  
y = 2 in s        # Este egal cu True  
z = 3 in s        # Este egal cu False  

Două motive principale pentru a folosi seturi:

În primul rând, operația in pe seturi este foarte eficientă. Când un set de date conține un număr foarte mare de elemente, căutarea elementelor sub formă de set este, evident, mai potrivită decât folosirea unei liste:

stopwords_list = ["a","an","at"] + hundreds_of_other_words + ["yet", "you"]  
"zip" in stopwords_list               # Eșuează, necesită verificarea fiecărui element  
stopwords_set = set(stopwords_list)  
"zip" in stopwords_set                # Căutare reușită, și foarte rapidă  

În al doilea rând, utilizarea seturilor este foarte convenabilă pentru a obține elementele distincte dintr-un set de date:

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]  

În practică, însă, seturile nu sunt utilizate la fel de frecvent ca dicționarele și listele.

Instrucțiuni condiționale

În majoritatea limbajelor de programare, poți folosi if pentru ramuri condiționale, ca aici:

if 1 > 2:  
    message = "if only 1 were greater than two…"  
elif 1 > 3:  
    message = "elif stands for 'else if'"  
else:  
    message = "when all else fails use else (if you want to)"  

Poți scrie instrucțiunile condiționale pe o singură linie, dar acest lucru este rar folosit:

parity = "even" if x % 2 == 0 else "odd"  

Instrucțiuni repetitive (buclări)

Bucla while

Bucla while în Python:

x = 0  
while x < 10:  
    print x, "is less than 10"  
    x += 1  

Bucla for

Mai frecvent, se utilizează bucla for-in:

for x in range(10):  
    print x, "is less than 10"  

Expresiile logice mai complexe pot folosi instrucțiunile continue și break:

for x in range(10):  
    if x == 3:  
        continue          # Trece direct la următoarea iterație a buclei  
    if x == 5:  
        break             # Ieșire completă din buclă  
    print x  

Rezultatul va afișa 0, 1, 2 și 4.

Valori de adevăr (Truthiness)

Variabilele booleene în Python funcționează similar cu cele din alte limbaje, singura diferență fiind că prima literă trebuie să fie întotdeauna majusculă:

one_is_less_than_two = 1 < 2      # Este True  
true_equals_false = True == False # Este False  

Python folosește None pentru a indica absența unei valori, similar cu null din alte limbaje:

x = None  
print x == None        # Afișează True, dar nu este stilul cel mai elegant  
print x is None        # Afișează True, este mai elegant  

Python îți permite să folosești alte valori în locul valorilor booleene. Următoarele sunt toate echivalente cu False:

În mod similar, există multe valori echivalente cu True, ceea ce îți permite să verifici foarte ușor liste goale, șiruri de caractere goale, dicționare goale și așa mai departe.

Desigur, dacă nu poți anticipa rezultatul, pot apărea erori în timpul utilizării:

s = some_function_that_returns_a_string()  
if s:  
    first_char = s[0]  
else:  
    first_char = ""  

O abordare mai simplă, cu același efect ca cea de mai sus:

first_char = s and s[0]  

Dacă prima valoare este adevărată, se va returna a doua valoare; în caz contrar, se va returna prima valoare.

În mod similar, dacă x poate fi un număr sau poate fi gol, poți obține un x care este cu siguranță un număr astfel:

safe_x = x or 0  

Python are și funcția all, care returnează True dacă toate elementele sunt True. Funcția any returnează True dacă cel puțin un element este True. De exemplu, pentru o listă în care fiecare element este “adevărat”, funcția all va returna True, altfel va returnând False:

all([True, 1, { 3 }])       # True  
all([True, 1, {}])          # False, {} este echivalent cu "False"  
any([True, 1, {}])          # True  
all([])                     # True, nu există niciun element echivalent cu "False"  
any([])                     # False, nu există niciun element echivalent cu "True"  

Lectură suplimentară: Sintaxa Python frecvent utilizată în știința datelor (Nivel avansat)