Python - wprowadzenie

13 - Biblioteki

Instalując Pythona, przy okazji instalujesz zestaw modułów o bardzo różnym zastosowaniu tworzących Bibliotekę Standardową Pythona (ang. The Python Standard Library). Poniżej umieściłem, krótki przegląd wybranych, znajdujących się w niej, modułów i niektórych zawartych w nich funkcji. Skupię się na tych, które mają związek z obliczeniami i procesami losowymi. Przynajmniej niektóre z pewnością uznasz za użyteczne. Będziemy się zresztą do wielu z nich, a także innych, odwoływać w przyszłości. Warto jednak samemu przejrzeć dokumentację Biblioteki Standardowej Pythona, którą znajdziesz pod adresem: https://docs.python.org/pl/3.8/library/index.html, Warto też zajrzeć do przewodnika: https://docs.python.org/pl/3.8/tutorial/stdlib.html. Na końcu tej lekcji skorzystamy także z innych bibliotek.

Moduł math

Moduł math zawiera (m.in.) funkcje matematyczne.

Dokumentacja: https://docs.python.org/3.8/library/math.html.

Importujemy poleceniem:

import math

Wybrane funkcje

math.ceil(x) - zaokrąglanie ,,w górę''.

math.ceil(3.14)
4

math.floor(x) - zaokrąglanie ,,w dół''.


math.floor(3.14)
3

math.modf(x) - zwraca część ułamkową i całkowitą w krotce, zachowując przy tym znak.

math.modf(-12.125)
(-0.125, -12.0)

math.fabs(x) - zwraca wartość absolutną liczby. Warto jednak zauważyć, że istnieje także wbudowana funkcja abs(x), zwróć uwagę, że math.fabs(x) zwraca liczbę zmiennoprzecinkową, nawet kiedy przekażemy jej liczbę całkowitą, abs(x) zwraca w takim przypadku liczbę całkowitą.

math.fabs(-12)
12.0
abs(-12)
12

math.fmod(x, y) - działa podobnie jak wyrażenie x % y, czyli zwraca resztę z dzielenia. Między oboma sposobami uzyskiwania są jednak pewne różnice (zob. dokumentacja), które sprawiają, że o ile wyrażenie x % y zaleca się dla liczb całkowitych, to jeśli chcemy uzyskać wynik dla liczb zmiennoprzecinkowe, to lepiej użyć funkcji math.fmod(x, y).

math.fmod(10, 3)
1.0

math.factorial(x) - zwraca silnię dla podanej liczby całkowitej.

math.factorial(5)
120

math.log(x[, podstawa]) - zwraca logarytm liczby x dla podanej podstawy. Jeśli podamy tylko liczbę x to zostanie zwrócony logarytm dla podstawy e (logarytm naturalny).

math.log(8,2)
3.0

math.sqrt(x) - zwraca pierwiastek kwadratowy z liczby x.

math.sqrt(9)
3.0

math.sin(x) - zwraca sinus dla kąta x podanego w radianach (!)

math.sin(0)
0.0

math.cos(x) - zwraca sinus dla kąta x podanego w radianach (!)

math.cos(0)
1.0

Wybrane stałe

math.pi - stała π (pi), czyli 3.141592…

math.pi
3.141592653589793

math.e - stała e, czyli 2.718281…

math.e
2.718281828459045

Moduł statistics

Jak nazwa wskazuje zawiera podstawowe funkcje statystyczne.

Dokumentacja: https://docs.python.org/3.8/library/statistics.html

Moduł importujemy poleceniem:

import statistics

W praktyce, ponieważ statistics to raczej długa nazwa, warto użyć przy imporcie aliasu, lub zaimportować potrzebne funkcje. Tu jednak będę używał pełnej nazwy modułu.

Wybrane funkcje

statistics.mean(data) - zwraca średnią arytmetyczną dla danych

dane = [1, 2, 3, 4]
statistics.mean(dane)
2.5

statistics.geometric_mean(data) - zwraca średnią geometryczną dla danych

dane = [1, 2, 3, 4]
statistics.geometric_mean(dane)
2.2133638394006434

statistics.median(data) - zwraca medianę dla danych

dane = [1, 2, 3, 4]
statistics.median(dane)
2.5

statistics.mode(data) - zwraca dominantę, czyli najczęściej występującą wartość. Jeśli jest kilka takich wartości, to zwracana jest pierwsza z nich.

dane = [3, 2, 3, 2, 2, 1, 5, 4]
statistics.mode(dane)
2
dane = [3, 2, 3, 2, 2, 3, 5, 4]
statistics.mode(dane)
3
dane = ['Ala', 'Phe', 'Gly', 'Ala', 'Arg', 'Gly']
statistics.mode(dane)
'Ala'
sekwencja = 'ACGCATGATG'
statistics.mode(sekwencja)
'A'

statistics.multimode(data) - zwraca dominantę, czyli najczęściej występującą wartość. Jeśli jest kilka takich wartości, to zwracane są wszystkie. Wynik zwracany jest w formie listy, nawet gdy jest to tylko jedna wartość.

dane = [3, 2, 3, 2, 2, 1, 5, 4]
statistics.multimode(dane)
[2]
dane = [3, 2, 3, 2, 2, 3, 5, 4]
statistics.multimode(dane)
[3, 2]
dane = ['Ala', 'Phe', 'Gly', 'Ala', 'Arg', 'Gly']
statistics.multimode(dane)
['Ala', 'Gly']
sekwencja = 'ACGCATGATG'
statistics.multimode(sekwencja)
['A', 'G']

statistics.pstdev(data) - odchylenie standardowe dla całej populacji

dane = [1.2, 1.3, 1.2, 1.5, 1.1, 1.3]
statistics.pstdev(dane)
0.1247219128924647

statistics.stdev(data) - odchylenie standardowe z próby

dane = [1.2, 1.3, 1.2, 1.5, 1.1, 1.3]
statistics.stdev(dane)
0.13662601021279464

statistics.pvariance(data) - wariancja dla całej populacji

dane = [1.2, 1.3, 1.2, 1.5, 1.1, 1.3]
statistics.pvariance(dane)
0.015555555555555553

statistics.variance(dane) - wariancja dla próby

dane = [1.2, 1.3, 1.2, 1.5, 1.1, 1.3]
statistics.variance(dane)
0.018666666666666665

Moduł random

Funkcje udostępniane przez moduł random są związane z generowaniem liczb pseudolosowych. Możliwe, że w tej chwili nasunęło Ci się pytanie: ,,Dlaczego liczby pseudolosowe a nie po prostu losowe?''.

Nie wchodząc w szczegóły, program komputerowy nie generuje liczb, które mają charakter rzeczywiście losowy. Generuje natomiast, używając algorytmów, zestawy liczb, które możliwie przypominają te prawdziwie losowe, ale mają charakter deterministyczny. Obliczenia zaczynają się od konkretnej liczby, zwanej ziarnem. Używając tego samego ziarna i tych samych algorytmów uzyskuje się taki sam zestaw liczb. Jeśli nie narzucamy ziarna funkcjom generującym liczby pseudolosowe, przy każdym wywołaniu używają innej wartości ziarna (korzystając z czasu systemowego), zatem otrzymujemy różne zestawy liczb.

Liczby pseudolosowe mają wiele zastosowań, od symulacji naukowych począwszy, na grach komputerowych skończywszy. Poświęcimy zatem temu modułowi nieco więcej uwagi. Dalej, dla uproszczenia, będę czasem używał terminu ,,losowy'' zamiast ,,pseudolosowy''.

Dokumentacja: https://docs.python.org/3.8/library/random.html

Moduł importujemy poleceniem:

import random

Wybrane funkcje

random.random() - podstawowa funkcja w module. Większość pozostałych bazuje własnie na niej. Zwraca pseudolosową liczbę float z zakresu [0.0, 1.0), czyli włącznie z 0.0 ale bez 1.0.

random.random()
0.5646673199495892

Sprobuj kilkukrotnie wywołać powyższe polecenie, za każdym razem powinna zostać zwrócona inna liczba.

Łatwo można uzyskać np. listę zawierającą liczby pseudolosowe:

# Generowanie 10 liczb pseudolosowych
liczby = []

for i in range(1, 11):
    liczby.append(random.random())

print(liczby)
[0.01712794026679565, 0.23080537747172514, 0.012876793612628279, 0.4662143843352964, 0.9962012130556934, 0.30002044902516023, 0.27076103471999935, 0.8657557128359082, 0.7221944878982216, 0.9720415877242576]

Można policzyć dla uzyskanych liczb np. średnią, zwiększmy jednak ich liczbę w liście do stu tysięcy. Przy okazji zauważ, że 100000 można zapisać jako 100_000, co zwiększa czytelność.

import statistics, random
liczby = []

for i in range(100_000):
    liczby.append(random.random())

print(f"Liczba liczb pseudolosowych: {len(liczby)}")
print(f"Średnia: {statistics.mean(liczby)}")
Liczba liczb pseudolosowych: 100000
Średnia: 0.5014882062952232

random.seed([a=None][, version=2]) - funkcja umożliwia zainicjalizowanie generatora liczb losowych. Jeśli argument a jest pominięty, lub ma wartość None to jako ziarno zostaje ustawiony czas systemowy. Można jednak tę wartość ustawić, co umożliwia uzyskanie powtarzalnych liczb pseudolosowych. Parametrem version funkcji, nie będziemy się zajmować.

Napiszmy krótki program, który pokaże jak to działa. Dla zwiększenie przejrzystości programu zaokrąglimy liczby pseudolosowe. Przy okazji zauważ, że ziarnem nie musi być liczba.

def pobierz_liczby(ziarno):
    liczby = []
    random.seed(ziarno)
    for i in range(5):
        liczba = round(random.random(), 2)
        liczby.append(liczba)
    print(f" Ziarno: {ziarno}, liczby: {liczby}")

pobierz_liczby(1000)
pobierz_liczby(1000)
pobierz_liczby(1001)
pobierz_liczby("BUM!")
pobierz_liczby(1000)
 Ziarno: 1000, liczby: [0.78, 0.67, 0.1, 0.35, 0.47]
 Ziarno: 1000, liczby: [0.78, 0.67, 0.1, 0.35, 0.47]
 Ziarno: 1001, liczby: [0.8, 0.06, 0.76, 0.86, 0.09]
 Ziarno: BUM!, liczby: [0.46, 0.87, 0.43, 0.78, 0.81]
 Ziarno: 1000, liczby: [0.78, 0.67, 0.1, 0.35, 0.47]

random.uniform(a, b) - zwraca liczbę zmiennoprzecinkową z zakresu między liczbą a (włącznie) i b (włącznie, lub nie - patrz dokumentacja)

random.uniform(1.0, 10.0)
5.812153673237898

random.gauss(mu, sigma) - funkcja, która może być bardzo pomocna dla różnych symulacji komputerowych procesów m.in. biologicznych, zwraca bowiem liczbę pseudolosową, ale zgodną z rozkładem Gaussa. Czyli np. jeśli wygenerujemy odpowiednią liczbę takich liczb i je przedstawimy na histogramie to powinniśmy uzyskać charakterystyczny kształt ,,dzwonu''. Na razie nie wiemy, co prawda jak taki histogram otrzymać, ale zrobimy to pod koniec lekcji. Parametry tej funkcji to: mu - średnia i sigma - odchylenie standardowe.

liczby = []
srednia = 10
sd = 1
for i in range(10):
    liczba = round(random.gauss(srednia, sd))
    liczby.append(liczba)

print(liczby)
[8, 10, 11, 10, 10, 8, 8, 12, 9, 9]

random.randrange([start,] stop[, step]) - funkcja zwraca pseudolosową liczbę całkowitą z zakresu od wartości start (opcjonalna, jeśli jej nie podamy to początkiem zakresu jest 0) do stop -1. Można też przekazać argument step oznaczający ,,krok'' co pozwala np. uzyskać pseudolosowe liczby parzyste:

liczby = []
for i in range(10):
    # Liczby pseudolosowe z zakresu od 1 do 9
    liczby.append(random.randrange(1, 10))
print(liczby)    
[9, 8, 7, 5, 1, 2, 1, 9, 6, 3]
liczby = []
for i in range(10):
    # Liczby pseudolosowe z zakresu od 0 do 9
    liczby.append(random.randrange(10))
print(liczby) 
[5, 2, 3, 7, 6, 7, 2, 0, 1, 5]
liczby = []
for i in range(10):
    # Liczby pseudolosowe z zakresu od 0 do 9, parzyste
    liczby.append(random.randrange(0, 10, 2))
print(liczby) 
[6, 8, 6, 2, 0, 2, 2, 4, 6, 6]

random.randint(a, b) - działa podobnie jak random.randrange(), ale w zakresie od liczby a do b włącznie. Brak jest trzeciego parametru oznaczającego ,,krok''.

liczby = []
for i in range(10):
    # Liczby pseudolosowe z zakresu od 0 do 5, parzyste
    liczby.append(random.randint(0, 5))
print(liczby) 
[5, 3, 0, 1, 5, 0, 1, 5, 0, 5]

random.choice(seq) - zwraca (pseudo)losowy element z podanej sekwencji (np. list, krotek itd.), można także przekazać łańcuch znaków, wtedy zostanie zwrócony znak.

zasady_azotowe = ['adenina', 'guanina', 'cytozyna', 'tymina', 'uracyl']
random.choice(zasady_azotowe)
'tymina'
dna = 'AGCTACGATTAGG'
random.choice(dna)
'T'

random.choices(population, weights=None, *, cum_weights=None, k=1) - zwraca listę k losowych elementów (mogą się powtarzać) z population (listy, krotki, zbiory, łańcuchy znaków etc…). Pozostałymi parametrami nie będziemy się zajmować.

Można np. wykorzystać tę funkcję do wygenerowania losowej sekwencji DNA:

zasady = 'ACGT'
sekwencja_lista = random.choices(zasady, k=10)
print(sekwencja_lista)
# Tworzymy łańcuch znaków z listy
sekwencja = ''.join(sekwencja_lista)
print(sekwencja)
['C', 'C', 'A', 'G', 'G', 'T', 'C', 'T', 'A', 'A']
CCAGGTCTAA

random.sample(population, k) - zwraca listę k losowych elementów z population ale bez powtórzeń. Wartość k nie może być większa niż liczba elementów do wylosowania.

Przykładowo, możemy wylosować trzy planety Układu Słonecznego:

planety = {'Merkury', 'Wenus', 'Ziemia', 'Mars', 'Jowisz', 'Saturn', 'Uran', 'Neptun'}

random.sample(planety, k=3)
['Ziemia', 'Neptun', 'Merkury']

Natomiast w ten sposób możemy wylosować 6 liczb z 49 (symulacja losowania Lotto):

random.sample(range(1,50), 6)
[45, 35, 22, 27, 9, 20]

random.shuffle(x[, random]) - losowo miesza elementy w x. W tym przypadku nie jest tworzony nowy obiekt z wynikiem, ale zmienia się przekazany funkcji obiekt x. Zatem nie można jej użyć dla obiektów niemodyfikowalnych, jak string czy tuple (krotka). W takich przypadkach można użyć sample(x, k=len(x)), który zwraca nową listę z wynikiem. Należy zauważyć, że przekazany obiekt musi być uporządkowany, nie możemy zatem użyć np. zbioru (set).

Pomieszajmy np. kolejność planet:

planety = ['Merkury', 'Wenus', 'Ziemia', 'Mars', 'Jowisz', 'Saturn', 'Uran', 'Neptun']
random.shuffle(planety)
print(planety)
['Merkury', 'Ziemia', 'Saturn', 'Mars', 'Wenus', 'Neptun', 'Uran', 'Jowisz']

Inne biblioteki

Poza Biblioteką Standardową Pythona dostępnych jest wiele innych bibliotek o bardzo różnych zastosowaniach. Niektóre z nich możesz już mieć zainstalowane wraz z instalacją środowiska Anaconda, inne możesz doinstalować samodzielnie. Jeśli używasz Anacondy można wiele z nich zainstalować (Terminal/Anaconda Prompt) poleceniem: conda install nazwa_pakietu. Jeśli nie używasz Anacondy można użyć np. narzędzia pip: pip install nazwa_pakietu. Instalacja danej biblioteki powinna być zresztą opisana na stronie internetowej jej poświęconej. Niektórych bibliotek będziemy używać w dalszej części kursu, teraz wspomnę tylko o dwu z nich, służących wizualizacji danych.

Biblioteka matplotlib

Matplotlib jest bardzo rozbudowaną biblioteką służąca wizualizacji w języku Python. Możemy z jej pomocą tworzyć np. wykresy, czy histogramy, co zobaczymy w poniższych, prostych przykładach. Warto zajrzeć na stronę WWW biblioteki, która znajduje się pod adresem: https://matplotlib.org.

W pierwszym przykładzie utworzymy wykres losowych zmian początkowej liczby (10). Przy każdej ze 100 iteracji pętli losowana jest liczba z zakresu -1 do 1, która jest dodawana do bieżącej wartości zmiennej l. Kolejne wartości l są dołączane do listy liczby. Na końcu jest tworzony, zapisany do pliku i wyświetlony (w tej kolejności!) wykres zmian wartości l. Wykres będzie zapisany w formacie wektorowym svg łatwym w dalszej edycji w takich programach jak np. Inkscape

# Import potrzebnych modułów
import matplotlib.pyplot as plt 
import random

# Lista przechowująca wartości l
liczby = []
# Przypisanie początkowej wartości 10 zmiennej l
l = 10

for i in range(100):
    # Ddanie wartości l do listy
    liczby.append(l)
    # Zmiana l o wylosowaną wartość z zakresu -1 - 1
    l += random.uniform(-1, 1)

# Tworzenie wykresu    
plt.plot(liczby)
# etykiety osi x i y
plt.xlabel('losowanie',fontsize=15)
plt.ylabel('wartość',fontsize=15)
# Zapisanie pliku w formacie svg
plt.savefig('wykres_liniowy_1.svg')
# Wyświetlenie wykresu
plt.show()

Drugi przykład będzie rozwinięciem poprzedniego. Na wykresie przedstawione zostaną dwie serie zmian losowych wartości. Ustawimy kolor i styl krzywych oraz dodamy legendę.

import matplotlib.pyplot as plt 
import random

liczby_1 = []
liczby_2 = []
l_1 = 10
l_2 = 10

for i in range(100):
    liczby_1.append(l_1)
    liczby_2.append(l_2)
    l_1 += random.uniform(-1, 1)
    l_2 += random.uniform(-1, 1)

# Linia zielona, ciągła, etykieta: "seria 1"   
plt.plot(liczby_1, 'g-', label='seria 1')
# Linia niebieska, kropkowana, etykieta: "seria 2"  
plt.plot(liczby_2, 'b:', label='seria 2')
plt.xlabel('losowanie',fontsize=15)
plt.ylabel('wartość',fontsize=15)
# legenda loc=0 oznacza optymalną lokalizację
plt.legend(loc=0)
plt.savefig('wykres_liniowy_2.svg')
# Wyświetlenie wykresu
plt.show()

W trzecim przykładzie pokażę, jak utworzyć wykres wartości losowych o rozkładzie Gaussa. Przykład ten jest na tyle podobny do poprzedniego, że nie wymaga raczej dodatkowych wyjaśnień. Tajemniczy może wydawać się parametr bins, który służy określeniu interwałów wartości. Więcej na ten temat, jak tez innych aspektów tworzenia histogramów za pomocą matplotlib można poczytać np. w dokumentacji: https://matplotlib.org/3.3.3/api/_as_gen/matplotlib.pyplot.hist.html. Tym razem zapiszemy plik w formacie png.

import matplotlib.pyplot as plt 
import random

liczby = []
srednia = 10
sd = 1

for i in range(100000):
    liczba = random.gauss(srednia, sd)
    liczby.append(liczba)

plt.hist(liczby, bins = 'auto')
plt.xlabel('wartość',fontsize=15)
plt.ylabel('liczba wartości',fontsize=15)
# Zapisanie pliku w formacie png
plt.savefig('wykres_2.png')
plt.show()

Biblioteka seaborn

W ostatnim przykładzie utworzymy podobny, jak w poprzednim, histogram, ale tym razem przy użyciu biblioteki seaborn, która bazuje na matplotlib, poszerzają, jest nowocześniejsza i bardziej przyjazna w użyciu. Strona domowa znajduje się pod adresem: https://seaborn.pydata.org.

import seaborn as sbn
import random
import math
liczby = []
srednia = 10
sd = 1
for i in range(100000):
    liczba = random.gauss(srednia, sd)
    liczby.append(liczba)
# Tworzenie histogramu
his = sbn.histplot(liczby)
# Ustawienie etykiet osi
his.set_xlabel("wartość", fontsize=15)
his.set_ylabel("liczba wartości", fontsize=15)
# Zapisanie histogramu w formacie png
his.get_figure().savefig('histogram_seaborn.png')

Zadanie:

Napisz program symulujący zmiany częstości alleli w zależności od mutacji. Zasady i założenia teoretyczne:

  • W populacji dany gen posiada dwa allele, tradycyjnie nazwijmy je: A i a.
  • Allel A mutuje w a z pewnym prawdopodobieństwem, oznaczmy je u.
  • Allel a mutuje w A z innym prawdopodobieństwem (v).
  • W populacji znajduje się początkowo określona liczba jednych i drugich alleli, np. po 100
  • Prawdopodobieństwa mutacji w obie strony i początkowe liczby alleli podaje użytkownik
  • Program ma symulować określoną liczbę pokoleń, w każdym pokoleniu każdy allel A może zmutować w a i odwrotnie.
  • Proporcja alleli w każdym pokoleniu mają być przedstawione liczbowo i ,,symbolicznie'' (zob. poniżej)
  • Pomiędzy wydrukiem dla kolejnych pokoleń powinna upłynąć sekunda. Znajdź odpowiednią funkcję w module time Standardowej Biblioteki Pythona.
  • Na końcu zapisz wykres przedstawiający zmiany proporcji alleli w czasie.

Przykładowy wydruk podczas działania programu:

 ----------- Pokolenie: 0 -----------
########################################
########################################
####################********************
****************************************
****************************************
A: 100 a: 100
----------- Pokolenie: 1 -----------
########################################
########################################
###################*********************
****************************************
****************************************
A: 99 a: 101
itd.
Last updated on 9 Dec 2020
Published on 9 Dec 2020