Referències:

Què és i per a què serveix NumPy ?

Python és un llenguatge fàcil d'usar, però per si sol no es pot utilitzar en aplicacions que processin dades numèriques.

En lloc d'utilitzar llistes natives de Python, la majoria d'aplicacions utilitzen els arrays que proporciona llibreria NumPy (Python Numerical) escrita a C, i que consta d'una interfície (API) Python.

Crea un projecte amb el nom de numeric, un entorn virtual venv i instal·la-hi numpy:

$ mkdir numeric
$ cd numeric
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install numpy

Inicia una sessió interactiva e importa numpy com a np:

$ python3
import numpy as np

Una llista Python pot contenir elements de diferents tipus:

> a = [1, "a", False]

I un array de NumPy "també".

Com podeu veure en aquest exemple, creem un array a partir d'una llista Python no homogènia:

> a = np.array([1, "a", False])

Això és possible perquè si no especifiques el tipus de dades de l'array, la funció array utilitza el tipus de dades adequat a la llista que rep com a paràmetre.

En aquest cas, l'array és de tipus <U21>

> a.dtype
dtype('<U21')

Si indiquem a la funció array que ha de crear un array de tipus int, la funció ens tornarà un error tal com podeu veure a continuació:

> a = np.array([1, "a", False], dtype=int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'a'

És interessant que ens corregeixi en aquest punt, així podem detectar més fàcilment errors per a corregir-los.

Homogeneïtat del tipus de dades en arrays de Numpy.

Numpy necessita que tots els elements de la matriu siguin del mateix tipus sempre.

Imaginem-nos que, a diferència de l'exemple anterior, dins l’array tenim un valor totalment diferent al que volem i no ens hem adonat.

Podriem mostrar l’array i ens el imprimiria bé (perquè no li hem dit explicitament el tipus de dades), però al fer qualsevol operació no ens deixaria.

temp_pacients = np.array([
    [39.6,38.4,36.5,36.4],
    [37.5,37.1,36.3,'x'],
])
print(temp_pacients)
print(temp_pacients.sum()) # Dona error aquí
numpy.core._exceptions._UFuncNoLoopError: ufunc 'add' did not contain a loop with signature matching types (dtype('<U32'), dtype('<U32')) -> None

Encara que els tipus semblin iguals, Numpy té els seus propis tipus anàlegs als de Python però no idèntics (np.int, np.complex…)

Per què usar NumPy?

Les matrius NumPy són més ràpides i compactes que les llistes de Python.

Una matriu consumeix menys memòria i és convenient fer servir. NumPy fa servir molta menys memòria per emmagatzemar dades i proporciona un mecanisme d'especificació dels tipus de dades.

Això permet el codi per optimitzar-se encara més.

Com és un array a Numpy ?

Una matriu és una estructura de dades central de la biblioteca NumPy. Una matriu és una quadrícula de valors i conté informació sobre les dades brutes, com localitzar un element i com interpretar un element.

Té una quadrícula d'elements que es poden indexar de diverses maneres.

https://numpy.org/doc/stable/user/quickstart.html#quickstart-indexing-slicing-and-iterating

En aquest exemple, el funcionament és similar a les matrius de Java i alguns altres llenguatges.

Els elements són tots del mateix tipus, conegut com l'array dtype.

Un array pot ser indexat per una tupla d'enters no negatius, per booleans, per una altra matriu, o per enters.

El ndim és el nombre de dimensions (el més habitual són 2 i 3 dimensions, però podrien ser més). El shape és una tupla de nombres enters que donen la mida de l'array al llarg de cada dimensió.

Una forma de definir les matrius de NumPy és des de llistes de Python, usant llistes enllaçades per a dades de dimensions dobles o superiors.

Per exemple:

a = np.array([1, 2, 3, 4, 5, 6])

o

b = a = np.array([1, 2][3, 4][5, 6])

Podem accedir als elements de la matriu usant claudàtors (square brackets).

Quan estigueu els elements d'accés, recordeu que la indexació a NumPy comença a 0.

Això significa que si voleu accedir al primer element de la vostra matriu, estareu accedint a l'element 0.

print(a[0])
[1 2 3 4]

Atributs i dimensions.

A NumPy, la classe que gestiona els arrays (matrius) es diu ndarray, que vol dir “N-dimensional array”.

Aquesta classe s'utilitza per a representar tant matrius (arrays de 2 dimensions) com vectors (arrays d'una dimensió). Per a matrius de 3D o més dimensions s'usa el terme tensor.

Un array es generalment un contenedor de tamany fixe d'elements del mateix tipus y tamany. El número de dimensions i elements en un array es defineix per la seva forma.

A NumPy, les dimensions es diuen eixos (axis). Això significa que si tens una matriu 2D que es veu així:

[[0., 0., 0.],
 [1., 1., 1.]]

La matriu té 2 eixos. El primer eix té una longitud de 2 i el segon eix té una longitud de 3.

Creació d'arrays

Mètodes i atributs: np.array(), np.zeros(), np.ones(), np.empty(), np.arange(), np.linspace(), dtype

Per crear una matriu de NumPy, podeu utilitzar la funció np.array().

Tot el que necessites fer per crear una matriu simple és passar-li una llista. També pots especificar el tipus de dades de la matriu (dtype)

np.array()

import numpy as np
a = np.array([1, 2, 3], dtype=np.int32)

Puede visualizar aquesta matriu de totes aquestes maneres:

data[1]
2
data[0:2]
array([1, 2])
data[1:]
array([2, 3])
data[-2:]
array([2, 3])

Exemple creació i accés array temperatures.

Imaginem-nos que volem crear un array de la temperatura d’uns pacients durant uns dies.

Cada fila serà un pacient (3 pacients), cada columna serà un dia (4 dies) en el que es pren la temperatura.

L'índex de les files i de les columnes comença per 0 i acaba pel número d’elements menys 1.

import numpy as np
temp_pacients = np.array([
    [39.6,38.4,36.5,36.4],
    [39.0,38.4,37.6,37.9],
    [37.5,37.1,36.3,36.0],
])

Per exemple, veiem que la temperatura del pacient 1 el primer dia és de 39,6; molta febre.

print(f'Temperatura pacient 1 el 1r dia.',temp_pacients[0][0])

Un altre exemple, si volem obtenir la temperatura del pacient 2 (fila 1) en tots els dies.

print(f'Temperatures del pacient 2, de tots els dies.')
print(temp_pacients[1])

I si volem veure la temperatura del dia 1 de cada pacient, usem la notació [:], que vol dir qualsevol valor:

print(f'Temperatures del dia 1, de tots els pacients.')
print(temp_pacients[:][1])

Si no se li especifica cap tipus, Numpy dedueix quins elements té quan es crea la matriu.

Podem saber quin tipus és amb la funció dtype:

print(temp_pacients.dtype)

Ens dona float64 en aquest cas, ens ha inferit (deduït) el tipus. És important saber amb quins tipus treballem, per evitar errors.

np.zeros() i np.ones()

A més de crear una matriu a partir d'una seqüència d'elements, podeu crear fàcilment un array plena de 0 o plena d'1s.

np.zeros(2)
array([0., 0.])
np.ones(2)
array([1., 1.])

np.empty()

També pots crear una matriu buida, La funció empty crea una matriu amb contingut aleatori i depèn de l'estat de la memòria. La raó d'utilitzar empty en comptes de zeros (o alguna cosa similar) és la velocitat - assegureu-vos domplir cada element després.

np.empty(2) 
array([3.14, 42.  ])  # may vary

Diferències entre empty i ones (o zeros):

Quina diferència hi ha entre zeros i empty? Temps de processament.

Si demanes un array amb tots zeros s’ha de gastar CPU per posar la secció de memòria assignada tota a zero.

En canvi si la demanes empty la secció de memòria estarà plena amb el que hi havia abans quan es va alliberar.

Si l’array el vas a omplir ara mateix, i l’omplés, és més eficient, però si no l’omplés del tot i te n'oblides tindras un conjunt de dades espuries, bugs, etc.

np.arange()

Podem crear una matriu amb una sèrie d'elements (semblant al range de les llistes de Pyhton):

np.arange(4)
array([0, 1, 2, 3])

I fins i tot un array que conté una gamma d'intervals uniformement espaiats. Per fer-ho, especificarà el primer número, últim número i la mida del pas.

np.arange(2, 9, 2)
array([2, 4, 6, 8])

També podeu utilitzar np.linspace() per crear una matriu amb valors que són espaiats linealment en un interval especificat:

np.linspace(0, 10, num=5)
array([ 0. , 2.5, 5. , 7.5, 10. ])

Crear i editar array durant la creació.

En ocasions no tindràs els valors de cap matriu ni llista, i generar-los no t'ajudarà a completar-los tots. En aquests casos t'has de buscar la vida per omplir-la dels valors que t'interessin.

Imagina que t'han demanat una matriu 8x8 que tingui un patró 0 (blanques) / 1 (negres) com si es tratés d'un tauler d'escacs. El resultat ha de ser:

[[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]
[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]
[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]
[0 1 0 1 0 1 0 1]
[1 0 1 0 1 0 1 0]]
import numpy as np
zero_picker = False;
tablero_ajedrez = np.empty([8,8], dtype=int)
for index_linea, linea in enumerate(tablero_ajedrez):
    for index_cuadrado, cuadrado in enumerate(linea):
        if(zero_picker == False):
            tablero_ajedrez[index_linea, index_cuadrado] = 0
        else:
            tablero_ajedrez[index_linea, index_cuadrado] = 1
        zero_picker = not zero_picker
    zero_picker = not zero_picker
print(tablero_ajedrez)

#tablero_ajedrez[1::2,::2] = 1
#tablero_ajedrez[::2,1::2] = 1

Tipus de dades de matrius de Numpy.

El tipus de dades numèric predeterminat de Python és float64 a no ser que dedueixi algún altre (int64).

Però pots especificar quin tipus triar, i de fet és molt recomanable que ho facis per motius d'eficàcia i rendiment.

Provem-ho:

Si vols pots crear un fitxer anomenat demo_np1.py, posar aquest codi, guardar-lo i arrencar-lo (amb terminal o el teu IDE preferit)

import numpy as np

xD = np.ones([2, 3])
print(xD)
print(xD.dtype)
print("The memory size of xD array is:",xD.itemsize*xD.size,"bytes")

x16 = np.ones([2, 3], dtype=np.int16)
print(x16)
print(x16.dtype)
print("The memory size of x16 array is:",x16.itemsize*x16.size,"bytes")
print("Size of one element from x16 is ",x16.itemsize, "bytes")

Els resultats que obtenim són:

[[1. 1. 1.]
 [1. 1. 1.]]
float64
The memory size of xD array is: 48 bytes
[[1 1 1]
 [1 1 1]]
int16
The memory size of x16 array is: 12 bytes
Size of one element from x16 is  2 bytes

Amb aquesta comprovació hem descobert 2 atributs de la matriu, que multiplicats ens donen el tamany en bytes de l'array Numpy:

  • x16.itemsize Tamany de cada element.
  • x16.size Número d'elements

Les variables de Python no té cap importància que ocupin 64 bits (és el tamany de paraula del processador), quan programis olvida’t de tot això (no és Java).

Només quan has de gestionar arrays de dades de gran volum es quan has de tenir en compte l’espai que ocuparan i minimitzarles.

I si en la RAM és important en les caches del processador és fonamental aprofitar al màxim l’espai! Estem fent big data, i quan gestionem grans volums de dades cal tenir en compte això, que és molt important.

  • https://realpython.com/numpy-tutorial/#optimizing-storage-data-types

Saber el tamany i forma d'un array.

Mètodes: ndarray.ndim, ndarray.size, ndarray.shape

ndarray.ndim

Número de dimensions de la matriu.

ndarray.size

Número total de elements de la matriz. És igual al producte dels elementos de la forma.

ndarray.shape

Tupla d'enters que indiquen el número d'elements que hi ha a cada dimensió de la matriu.

Per exemple, si tens un Conjunt de 2-D amb 2 files i 3 columnes, la forma és (2, 3).

Provarem els 3 atributs amb aquest array:

array_example = np.array([[[0, 1, 2, 3],
                           [4, 5, 6, 7]],
                          [[0, 1, 2, 3],
                           [4, 5, 6, 7]],
                          [[0 ,1 ,2, 3],
                           [4, 5, 6, 7]]])

print(array_example.ndim)
3

print(array_example.size)
24

print(array_example.shape)
(3, 2, 4)

Es pot remodelar una matriu ?

I tant, usant arr.reshape() podràs definir una nova forma a la matriu sense canviar les dades.

La única condició és que la nova matriu ha de tenir el mateix tamany que la original.

Per exemple, pots remodelar una matriu de (6, 2) a (3, 4) perquè el tamany (size) en ambdues és el mateix, 12.

Aquí tenim un exemple per transformar una matriu (6, 1) a (3, 2):

a = np.arange(6)

print(a)
[0 1 2 3 4 5]

b = a.reshape(3, 2)

print(b)
[[0 1]
 [2 3]
 [4 5]]

Preguntes tipus test i exercic Numpy - Part 1.

Pr1- Quina és la sintaxi corrrecta per mostrar el número 8 ??

arr = np.array([[1,2,3,4,5], [6,7,8,9,10]])

  1. print(arr[7,2])
  2. print(arr[0,2])
  3. print(arr[1,2])
  4. print(arr[2,3])

Pr2- Quina és la sintaxi corrrecta per mostrar els números [3, 4, 5] ?? arr = np.array([1,2,3,4,5,6,7])

  1. print(arr[2,5])
  2. print(arr[3,6])
  3. print(arr[2,4])
  4. print(arr[2,6])

Pr3- Què mostrarà per pantalla el següent codi ?

A = np.array([
    ['a', 'b', 'c'],
    ['d', 'e', 'f'],
    ['g', 'h', 'i']
])

print(A[:, :2])

Pr4- Quina és la forma correcta d'obtenir el tipus de dades a Numpy de l'array arr ?

  1. arr.types
  2. arr.shape
  3. arr.type
  4. arr.dtype

Pr5- Quina relació hi ha entre la mida dels objectes (com ara llistes i tipus de dades) a la memòria de la biblioteca estàndard de Python i la biblioteca NumPy? Sabent això, quines són les implicacions per al rendiment?

  1. Els objectes Python estàndard ocupen molta més memòria per emmagatzemar que els objectes NumPy; Les operacions sobre objectes estàndard comparables de Python i NumPy es completen aproximadament al mateix temps.

  2. Els objectes NumPy ocupen molta més memòria que els objectes Python estàndard; Les operacions sobre objectes NumPy es completen molt ràpidament en comparació amb objectes comparables en Python estàndard.

  3. Els objectes NumPy ocupen molta menys memòria que els objectes Standard Python; Les operacions sobre objectes Python estàndard es completen molt ràpidament en comparació amb objectes comparables a NumPy Object.

  4. Els objectes Python estàndard ocupen més memòria que els objectes NumPy; Les operacions sobre objectes NumPy es completen molt ràpidament en comparació amb objectes comparables en Python estàndard.

Ex1. Ens han donat aquestes llistes que representen els resultats de proves de glucosa en sang (mg/dL) de 4 pacients en 3 moments diferents del dia (matí, tarda, nit).

[95, 105, 99], # Pacient 1 [89, 112, 102], # Pacient 2 [110, 129, 115],# Pacient 3 [100, 108, 104] # Pacient 4

Fes un programa que realitzi el següent:

  • Guarda tots els resultats en un array de numpy del tipus més adient i que ocupi el mínim espai possible.
  • Mostra el contingut, la forma de l'array i el tamany de memòria que ocupa.
  • Mostra les dades de l'últim pacient.
  • Mostra les dades de tots els paients pel matí.

Pr1 - 3, Pr2 - 1, Pr3 - 3, Pr4 - 2, Pr5 - 4.

Ex1.

import numpy as np
# Dades dels resultats de glucosa en sang dels pacients
resultats = [
    [95, 105, 99],   # Pacient 1
    [89, 112, 102],  # Pacient 2
    [110, 120, 115], # Pacient 3
    [100, 108, 104]  # Pacient 4
]
resultats_array = np.array(resultats, dtype="int16")

print("Contingut de l'array:", resultats_array)
print("Forma de l'array:", resultats_array.shape)
print("Tamany de memòria que ocupa:", resultats_array.nbytes, "bytes")

print("\nDades de l'últim pacient:")
print(resultats_array[-1])
print("\nDades de tots els pacients pel matí:")
print(resultats_array[:, 0])

Operacions amb valors de matrius.

Ordenació

Provem els mètodes np.sort() i np.concatenate()

Ordenar un element és simple amb np.sort().

Ho provarem amb aquesta matriu de números desordenats:

arr = np.array([2, 1, 5, 3, 7, 4, 6, 8])
np.sort(arr)
array([1, 2, 3, 4, 5, 6, 7, 8])

A més d'ordenar, que torna una còpia ordenada d'un array, podeu utilitzar:

  • argsort, que és un tipus indirecte al llarg d'un eix especificat,
  • lexsort, que és un tipus estable indirecte en múltiples tecles,
  • searchsorted, que trobarà elements en una matriu ordenada, i
  • partition, que és un tipus parcial.

També funciona amb matrius amb text com a

Concaternació

Si inicies aquestes matrius, puedes concatenar-les con np.concatenate().

a = np.array([1, 2, 3, 4])

b = np.array([5, 6, 7, 8])

np.concatenate((a, b))

array([1, 2, 3, 4, 5, 6, 7, 8])

Un altre exemple:

x = np.array([[1, 2], [3, 4]])

y = np.array([[5, 6]])

np.concatenate((x, y), axis=0)
array([[1, 2],
       [3, 4],
       [5, 6]])

Consulta més detalls sobre els mètodes sort i concaternate a la web oficial de Numpy:

  • https://numpy.org/doc/stable/reference/routines.sort.html
  • https://numpy.org/doc/stable/reference/generated/numpy.concatenate.html

Transposar matrius.

Ho pots fer amb l'atribut T o el mètode .transpose()i serveix per intercanviar els continguts dels eixos.

Si empiezas con esta matriz:

arr = np.arange(6).reshape((2, 3))

arr
array([[0, 1, 2],
       [3, 4, 5]])

Puedes transponer tu matriz con arr.transpose().

arr.transpose()
array([[0, 3],
       [1, 4],
       [2, 5]])

También puedes usar arr.T:

arr.T
array([[0, 3],
       [1, 4],
       [2, 5]])

Invertir ordre: flip

NumPys np.flip() la función le permite voltear, o invirtió, el contenido de un array a lo largo de un eje. Cuando se usa np.flip(), especifique el array que le gustaría a la inversa y el eje. Si no especifica el eje, NumPy revertirá el contenido a lo largo de todos los ejes de su matriz de entrada.

Invertir contingut matriu 1D

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
reversed_arr = np.flip(arr)
print('Reversed Array: ', reversed_arr)
Reversed Array:  [8 7 6 5 4 3 2 1]

Invertir contingut matriu 2D

arr_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
reversed_arr = np.flip(arr_2d)
print(reversed_arr)
[[12 11 10  9]
 [ 8  7  6  5]
 [ 4  3  2  1]]

Invertir files d'una matriu 2D

reversed_arr_rows = np.flip(arr_2d, axis=0)

print(reversed_arr_rows)
[[ 9 10 11 12]
 [ 5  6  7  8]
 [ 1  2  3  4]]

Invertir columnes d'una matriu 2D

reversed_arr_columns = np.flip(arr_2d, axis=1)

print(reversed_arr_columns)
[[ 4  3  2  1]
 [ 8  7  6  5]
 [12 11 10  9]]

Invertir només una fila d'una matriu 2D

arr_2d[1] = np.flip(arr_2d[1])
print(arr_2d)
[[ 1  2  3  4]
 [ 8  7  6  5]
 [ 9 10 11 12]]

Invertir només una columna d'una matriu 2D

arr_2d[:,1] = np.flip(arr_2d[:,1])

print(arr_2d)
[[ 1 10  3  4]
 [ 8  7  6  5]
 [ 9  2 11 12]]

Documentació del mètode flip:

  • https://numpy-org.translate.goog/doc/stable/reference/generated/numpy.flip.html

Còpies de matrius: view vs copy.

Per a fer còpies netes de matrius Numpy proporciona 2 mètodes: view i copy

El view (o l'operador =) serveix per crear un nou objecte d'array que agafa les mateixes dades que l'array original (còpia superficial).

Les vistes són un concepte important de NumPy. Ens permeten salvar memòria i són més ràpides.

Aquest concepte és anàleg a les vistes que proporcionen diverses bases de dades basades SQL.

El que és important saber és que modificar les dades d'una vista també modifica la matriu original.

Creem una matriu a i creem una còpia d'alguns valors en una matriu vista b1. Veurem com els valors d'a canvien quen s'edita un valor de b1 (i viceversa).

a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])

b1 = a[0, :]

b1
array([1, 2, 3, 4])
b1[0] = 99

b1
array([99,  2,  3,  4])
a
array([[99,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12]])

En canvi, si uses el mètode copy farà una còpia completa de l'array y les dades. Així, si edites un valor de la còpia no canvia a la matriu original (ni al revés).

Pots fer una còpia amb aquesta instrucció i provar-ho:

b2 = a.copy()
b2[0] = 50
print(b2)
```py
array([50,  2,  3,  4])

```py
print(a)

array([[99, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12]])

Remodelació i aplanament de conjunts multidimensionals

Hi ha dues maneres populars d'aplanar un array (convertir-lo a un array 1D): .flatten() i .ravel().

Ens pot interessar si volem guardar tota la matriu en una llista.

La diferència principal entre tots dos és que la nova matriu creada usant ravel() és en realitat una referència a la matriu de pares (és a dir, una vista). Això significa que qualsevol canvi a la nova matriu també afectarà la matriu original. En canvi, quan uses flatten fas una còpia dura exacta i llavors els canvis a la nova matriu no afecten a la matriu original.

Provem de fer la còpia amb el mètode flatten:

x = np.array([[1 , 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
x.flatten()
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])
a1 = x.flatten()
a1[0] = 99

print(x)  # Original array
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

print(a1)  # New array
[99  2  3  4  5  6  7  8  9 10 11 12]

Provem de fer la vista amb el mètode ravel:

a2 = x.ravel()

a2[0] = 98

print(x)  # Original array
[[98  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

print(a2)  # New array
[98  2  3  4  5  6  7  8  9 10 11 12]

Valors constants de numpy.

Numpy conté constants que són interessants en el món de la ciència; com np.pi (el famós número PI=3,14159…), logaritmes i el valor Inf, per a especificar un número infinit.

>>> print(round(np.pi,6))
3.141593
>>> np.array([1]) / 0
array([ Inf])

Més informació sobre constants de Numpy:

  • https://numpy.org/doc/stable/reference/constants.html#numpy.e

Tractament valors no existents: NaN.

Un dels problemes de tractar amb dades massives és que és freqüent trobar-te una sèrie en que hi ha algún buit perquè la dada no s’ha pogut registrar o és desconeix.

En llenguatges de programació (pex Java) o de bases de dades (SQL) existeix el valor nul. Per exemple, una persona pot tenir l’atribut telefon amb valor null. Però aquest nul que vol dir? Que no té telèfon o que no sabem si te telèfon perquè no li hem preguntat o no l’ha registrat?

En el cas de seqüències de dades és evident que no és el mateix cas, hi ha un buit i aquest buit sempre estarà, però s’ha d’omplir amb alguna cosa, i està clar que no és un número (no ens podem inventar les dades numèriques). D’aquí ve el nom NaN, Not a Number (no és un número). Que és? Ves a saber 🤔

Pots pensar que els de Python, concretament els de NumPy som una mica frescos, però es una solució molt pensada i raonada. En un llenguatge com Java o C en un array de números només pot haver-hi números; per tant, si falten dades en una seqüència has de reflectir aquest fet posant 0 o -1 o ves a saber què, el que suposa a la llarga problemes i bugs.

Et poden dir que Python no es un gran llenguatge perquè no té tipus forts com Java, però amb arrays de dades numèriques és tot el contrari 🙂.

Dins del contingut indeterminat, hi ha 3 conceptes diferents:

  • NaN: Not a Number (infinit, Indeterminat). La dada està calculada. Concepte matemàtic.
  • NA: Not Avalaible (No disponible). La dada no hi és, no existeix. Concepte estadístic.
  • None: És un objecte que fa el mateix que NA. No és eficient.

Per eficiencia a Python i Numpy s'utilitza NaN quan vol dir NA. Amb numpy representem el valor NaN amb la constant np.nan https://numpy.org/doc/stable/reference/constants.html#numpy.nan

És important definir alguna estratègia de què fer amb aquests registres amb valor np.nan, depèn d’importància de les dades.

Tal com s’explica en aquest article sobre els valors NaN a Numpy i Pandas, qualsevol operació sobre tot un array que té almenys un NaN té com a resultat un NaN perquè no sabem de quina manera afectarà a la operació suma o qualsevol altre? Està completament indefinit!

No obstant, com que l’opció més habitual a l’hora de tractar matrius és ignorar el que molesta perquè et fa pensar i qüestionar coses, l’objecte array permet fer algunes operacions ignorant els NaN: Per exemple array.nansum, etc

Ampliarem aquestes reflexions sobre què fer amb columnes que tinguin algun valor NaN quan veiem la llibreria Pandas.

El concepte de NaN existia fins i tot abans de la creació de Python. L'estàndard IEEE per a l'aritmètica de coma flotant (IEEE 754) va introduir NaN el 1985.

NaN és un valor especial de coma flotant que no es pot convertir a cap altre tipus que no sigui flotant. Per observar les propietats de NaN, creem una matriu Numpy amb valors de NaN.

import numpy as np
arr = np.array([1, np.nan, 3, 4, 5, 6, np.nan])
pritn(arr)

Sortida: [ 1. nan 3. 4. 5. 6. nan]

Operacions matemàtiques sobre una matriu Numpy amb NaN

Intentem cridar algunes funcions bàsiques a la matriu Numpy .

cprint(arr.sum()) print(arr.max())


Sortida:
nan
nan

Una opció bastant freqüent és que en la sèrie concreta de dades que vas a analitzar puguis ignorar les dades ausents. Per aquest motiu, Numpy ofereix mètodes que ignoren els valors de NaN mentre es realitzen operacions matemàtiques.

### Ignorar els valors de NaN mentre es realitzen operacions matemàtiques en una matriu.

Numpy us ofereix mètodes com np.nansum() i np.nanmax() per calcular la suma i el màxim després d'ignorar els valors de NaN a la matriu.

```py
$ np.nansum(arr)
19.0
$ np.nanmax(arr)
6.0

Si tens activat l'autocompleció al vostre IDE, podràs veure la llista d'opcions mentre treballeu amb np.nan. nanmean, nanmin ...

Comprovació dels valors de NaN

Per comprovar els valors de NaN en una matriu Numpy, podeu utilitzar el mètode np.isnan(). Això genera una màscara booleana de la mida de la matriu original.

La matriu de sortida té True per als índexs que són NaN a la matriu original i False per a la resta.

Per exemple, en la nostra matriu; si apliquem la instrucció print(np.isnan(arr)) [1, np.nan, 3, 4, 5, 6, np.nan, 8]

La sortida serà: [False True False False False False True]

Per saber quants Nan tenim en una matriu:

arr = np.array([1, np.nan, 3, 4, 5, 6, np.nan, 8])
print(np.isnan(arr))
print("Nan values in arr = ", np.count_nonzero(np.isnan(arr)))

Resultat: [False True False False False False True] Nan values in arr = 2

Són dos NaN iguals entre si?

Aquesta pot ser una pregunta confusa. Intentem respondre-ho executant algun codi Python. a = np.nan b = np.nan

Aquestes dues declaracions inicialitzen dues variables, a i b amb nan. Intentem equiparar els dos. $ a == b False

A Python també tenim l' operador is . Intentem utilitzar-ho per comparar les dues variables. $ a is b True

La raó d'això és que l'operador == compara els valors dels operands i comprova la igualtat de valors. L’operador is, en canvi, comprova si els dos operands fan referència o no al mateix objecte. De fet, pots imprimir els ID de a i b i veure que es refereixen al mateix objecte.

$ id(a) 139836725842784

$ id(b) 139836725842784

Filtratge, edició i esborrat d'elements d'arrays.

Abans d'explicar com s'elimina contingut als arrays, hem de recordar com funcionen per dins.

Internament, els arrays de Numpy funcionen amb C(llenguatge multiplataforma inclòs en tots el SO) i el que fa aquest és agafar un tros de memòria RAM on es pugui guardar tot l'array de forma consecutiva.

Per tant, si la edició de l'array consisteix en intercanviar un valor per un altre, es podrà fer automàticament perquè al final l'array seguirà ocupant el mateix.

En canvi, tots els canvis que impliquin un canvi en la memòria reservada (eliminar files, columnes...) crearan automàticament una còpia modificada de l'array, per no perdre informació accidentalment.

Esborrat files o columnes.

Provarem tot això amb el mètode per eliminar files o columnes concretes, np.delete. Ens permet eliminar la segona fila (primera = 0, segona = 1, …). Si posem axis=0 eliminarà files, axis=1 columnes.

arr = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
np.delete(arr, 1, axis=0)
print(arr)

Resultat.

[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

Com és que no s’ha eliminat la fila que li hem dit ? No ha passat res! Perquè Numpy no pot retornar l’array modificat, sinó una còpia modificada.

Si ens interessa mantenir l’array original, podem fer-ho amb la instrucció:

arr_cpy = np.delete(arr, 1, axis=0)
print(arr_cp)

[[ 1  2  3  4]
 [ 9 10 11 12]]

En canvi, si només volem tenir el nou array, podem sobreescriure l’array original i així estalviar memòria:

arr = np.delete(arr, 1, axis=0)
print(arr)

Selecció valors nou array per index.

Podeu indexar i llescar (retallar) els arrays NumPy de la mateixa manera que podeu tallar llistes de Python.

data = np.array([1, 2, 3])

data[1]
2

data[0:2]
array([1, 2])

data[1:]
array([2, 3])

data[-2:]
array([2, 3])

Selecció valors nou array per condicions.

Si voleu seleccionar valors de la vostra matriu que compleixin certes condicions, és molt senzill amb NumPy.

Per exemple, pots crear una nova matriu només amb els valors menors de 5.

a = np.array([[1 , 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print(a[a < 5])
[1 2 3 4]

També podeu seleccionar, per exemple, números que siguin iguals o superiors a 5, i utilitzar aquesta condició per indexar una matriu.

five_up = (a >= 5)
print(a[five_up])
[ 5  6  7  8  9 10 11 12]

Seleccionar elements divisibles per 2:

divisible_by_2 = a[a%2==0]

print(divisible_by_2)
[ 2  4  6  8 10 12]

O pots seleccionar elements que compleixin diverses condicions usant operadors :

c = a[(a > 2) & (a < 11)]
print(c)
[ 3  4  5  6  7  8  9 10]

També és important saber quins elements d'un array compleixen les condicions o no; per filtrar números o altres valors categòrics.

five_up = (a > 5) | (a == 5)

print(five_up)
[[False False False False]
 [ True  True  True  True]
 [ True  True  True True]]

Si l'element que busques no existeix a l'array, llavors el retornat La varietat d'índexs estarà buida. Per exemple:

not_there = np.nonzero(a == 42)
print(not_there)
(array([], dtype=int64), array([], dtype=int64))

Fusionar diverses matrius.

La sección cobreix: slicing and indexing, np.vstack(), np.hstack(), np.hsplit()

Posts crear una matriu nova a partir d'una secció especificant on voleu tallar la vostra matriu, tal i com feiem en llistes de Python.

a = np.array([1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
arr1 = a[3:8]
arr1
array([4, 5, 6, 7, 8])

També pots apilar dues matrius existents, tanto vertical (vstack) como horizontalmente (hstack). Provem-ho amb aquestes dues:

a1 = np.array([[1, 1],
               [2, 2]])

a2 = np.array([[3, 3],
               [4, 4]])

Pots apilar-los verticalment con vstack:

np.vstack((a1, a2))
array([[1, 1],
       [2, 2],
       [3, 3],
       [4, 4]])

O apilar-los horizontalment con hstack:

np.hstack((a1, a2))
array([[1, 1, 3, 3],
       [2, 2, 4, 4]])

Dividir matriu/s.

Es pot dividir una matriu en diverses matrius més petits fent servir hsplit.

Suposem que tenim aquesta matriu:

x = np.arange(1, 25).reshape(2, 12)
x
array([[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12],
       [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]])

Per dividir-la en 3 matrius:

np.hsplit(x, 3)
  [array([[ 1,  2,  3,  4],
         [13, 14, 15, 16]]), array([[ 5,  6,  7,  8],
         [17, 18, 19, 20]]), array([[ 9, 10, 11, 12],
         [21, 22, 23, 24]])]

Exercici 1. Transforma aquest array d’1x12 (temperatures de cada mes) a 2 arrays de 2 files i 3 columnes. Un array de 3 dimensions.

temperatures = np.array([
29.3, 42.1, 18.8, 16.1, 38.0, 12.5, 12.6, 49.9, 38.6, 31.3, 9.2, 22.2
])

Solució:

temperatures.reshape(3, 4)
temperatures.shape
(2, 2, 3)
temperatures
array([[[29.3, 42.1, 18.8],
        [16.1, 38. , 12.5]],

       [[12.6, 49.9, 38.6],
        [31.3,  9.2, 22.2]]])

Operacions numèriques amb matrius

Secció: suma, resta, multiplicació, divisió, potència.

Primer, provarem les operacions amb dos arrays, un es diu "data". y un altre que es diu "ones" Podem fer la suma únicament amb l'operador +.

data = np.array([1, 2])
ones = np.ones(2, dtype=int)
print(data + ones)
[2 3]

Pots fer més operacions (restar, multiplica,r dividir)

data - ones
array([0, 1])

data * data
array([1, 4])

data / data
array([1., 1.])

sum()

Si vols calcular la suma dels elements en una array, va molt bé el mètode sum(). Funciona per arrays de qualsevol dimensió.

a = np.array([1, 2, 3, 4])
a.sum()
10

Per fer sumes d'arrays bidimensionals pots especificar l'eix on començar:

b = np.array([[1, 1], [2, 2]])

Suma per eix de les files:

b.sum(axis=0)
array([3, 3])

Suma per eix de les columnes:

b.sum(axis=1)
array([2, 4])

En moltes ocasions, ens pot interessar fer operació entre un array i un sol nombre (també anomenat operació entre un vector i un escalar) o entre conjunts de dues mides diferents.

Per exemple, tenim una matriu amb informació sobre la distància en quilòmetres de ciutats i voleu convertir-la a milles (terrestres).

Podeu realitzar aquesta operació amb:

data = np.array([1.0, 2.0])
data * 1.6
array([1.6, 3.2])

Per provar les potències, practicarem en les que més coneixem els informàtics:

[ 2^n ]\

Per exemple, ja sabeu que amb 8 bits es poden representar [ 2^8 = 256 ]\ 2 ^ 8 = 256 valors. Per exemple, podem tenir una variable que tingui valors entre -128 i 127. I també que 8 bits són 1 Byte.

També sabeu que [ 2^16 = 65536 bits = 2 Bytes ].

Llavors, creeu una matriu de de 8 x 2; que tingui el següent aspecte:

[[    2     4     8    16    32    64   128   256]
 [  512  1024  2048  4096  8192 16384 32768 65536]]
quadlist = [2**i for i in range(1, 17)]
quadarr = np.array(quadlist).reshape(2,8)
print(quadarr)

Exemple problema de matrius.

Una firma d'automòbils disposa de dues plantes de fabricació una a Espanya i una altra a Anglaterra, on fabrica dos models de cotxes M1 i M2, de tres colors x, i, z.

La seva capacitat de producció diària a cada planta està donada per les matrius següents (A per a Espanya i B per a Anglaterra).

Matriu A

[[ 300, 95],
 [ 250, 100],
 [ 200, 100]]

Matriu B

[[ 190, 90],
 [ 200, 100],
 [ 150, 80]]

Crea les matrius i respon:

a. Determinar la representació matricial de la producció total per dia

b. Si s'eleva la producció a Espanya un 20% i es disminueix a Anglaterra un 10% quina matriu representa la nova producció total?

Solució (codi i resultat):

ar_vendes_es = np.array([
    [ 300, 95],
    [ 250, 100],
    [ 200, 100]
])
ar_vendes_uk = np.array([
    [ 190, 90],
    [ 200, 100],
    [ 150, 80]
])

print('a) Determinar la representación matricial de la producción total por día.')
ar_vendes_total_1 = ar_vendes_es + ar_vendes_uk
print(ar_vendes_total_1)

print('b) Si se eleva la producción en España un 20% y se disminuye en Inglaterra un 10% ¿qué matriz representa la nueva producción total?')
ar_vendes_es_2 = ar_vendes_es*1.2
ar_vendes_uk_2 = ar_vendes_uk*0.9

ar_vendes_total_2 = ar_vendes_es
print(ar_vendes_total_2)

Resultat A. [[490 185] [450 200] [350 180]]

Resultat B [[300 95] [250 100] [200 100]]

Si les dimensions en un problema no són compatibles, aconseguireu un ValueError.

Generar números aleatoris

L'ús de la generació de nombres aleatoris (en realitat, números pseudoaleatòries repetibles) és una part important de la configuració i avaluació de molts algorismes numèrics i d'aprenentatge automàtic.

Amb Generator.integers, pots generar números enters aleatoris. Pots generar una matriu de 2 x 4 d'enters aleatoris entre 0 i 4 amb:

rng = np.random.default_rng()
a = rng.integers(5, size=(2, 4)) # may vary
print(a)

També pots establir endpoint=True per a incloure el número més alt (5 en el nostre cas).

En versions anteriors de Numpy els números aleatoris es creen d'aquesta manera:

matrix_1 = np.random.randint(10, size=(6,4))

I encara es pot fer de les dues maneres per motius de retrocompatibilitat, però a la documentació ens animen a usar Generator.integers i els nous mètodes.

Operacions estadístiques amb matrius

Funcions disponibles: màxim, mínim, suma, mitjana, etc...

NumPy també realitza funcions d'agregació, que són funcions que s'apliquen sobre més d'un element de l'array (una o més files, una o més columnes, o l'array sencer) Les funcions d'agregació més habituals són: min, max, i sum.

També pots calcular fàcilment la mitjana aritmètica mean, std (desviació estàndard), i més.

Creem aquest array per a provar-los:

a = np.array([[0.45053314, 0.17296777, 0.34376245, 0.5510652],
              [0.54627315, 0.05093587, 0.40067661, 0.55645993],
              [0.12697628, 0.82485143, 0.26590556, 0.56917101]])

És molt comú voler sumar tots els elements d'una fila o columna.

Per defecte, cada La funció d' agregació NumPy retornarà l'agregat (suma) de tota la matriu.

A trobar la suma o el mínim dels elements de la seva matriu, executa:

a.sum()
4.8595784

O:

a.min()
0.05093587

Pots especificar en quin eix s'aplica la funció d'agregació. Per exemple, pot trobar el valor mínim dins de cada columna especificant axis = 0.

a.min(axis=0)
array([0.12697628, 0.05093587, 0.26590556, 0.5510652 ])

Els quatre valors esmentats anteriorment corresponen al nombre de columnes de la matriu. Amb una matriu de quatre columnes, obtindre, quatre valors com a resultat.

Pot agregar tots els valors en una matriu i pot agregar-los a través columnes o files amb el paràmetre axis. Per il·lustrar aquest punt, mira un conjunt de dades lleugerament modificat:

data = np.array([[1, 2], [5, 3], [4, 6]])

data
array([[1, 2],
       [5, 3],
       [4, 6]])

data.max(axis=0)
array([5, 6])

data.max(axis=1)
array([2, 5, 6])

Un cop hagi creat les seves matrius, pot afegir-les i multiplicar-les usant operadors aritmètics si tens dues matrius que són de la mateixa mida.

data = np.array([[1, 2], [3, 4]])

ones = np.array([[1, 1], [1, 1]])

data + ones
array([[2, 3],
       [4, 5]])

Així pots visualitzar el màxim, mínim i sumatori dels valors d'un array sencer:

data.max()
6
data.min()
1
data.sum()
21

També podem generar la mitjana i la desviació típica o estàndard d'un vector amb 300 números aleatoris:

import numpy as np
vector = np.random.randint(100, size=300)
print("Average: %.2f" % float(np.average(vector)))
print("Deviation: %.2f" % float(np.std(vector)))

Average: 47.88
Deviation: 29.78

Com aconseguir valors únics.

Vostè pot trobar els elements únics en una matriu fàcilment amb np.unique.

Per exemple, si comences amb aquesta matriu pots fer servir np.unique per imprimir els valors únics de la seva matriu:

a = np.array([11, 11, 12, 13, 14, 15, 16, 17, 12, 13, 11, 14, 18, 19, 20])
unique_values = np.unique(a)
print(unique_values)
[11 12 13 14 15 16 17 18 19 20]

Si en compte dels valors ens interessen els index, tenim el paràmetre return_index=True.

unique_values, indices_list = np.unique(a, return_index=True)
print(indices_list)
[ 0  2  3  4  5  6  7 12 13 14]

Per obtenir el recompte de freqüències el més útil és usar l'argument return_counts dins d'np.unique()

unique_values, occurrence_count = np.unique(a, return_counts=True)
print(occurrence_count)
[3 2 2 2 1 1 1 1 1 1]

Veiem un exemple amb matrius 2D:

a_2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [1, 2, 3, 4]])
unique_values = np.unique(a_2d)
print(unique_values)
[ 1  2  3  4  5  6  7  8  9 10 11 12]

Si no has especificat l'eix (paràmetre axis), la matriu 2D s'aplanarà (es transformarà a 1D).

Per trobar les files úniques, especifica axis = 0 i per a les columnes axis = 1.

unique_rows = np.unique(a_2d, axis=0)

print(unique_rows)
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]

Una de les dades que més ens interessa en estadística és la moda, que és saber quin (o quins) valors es repeteixen més i quantes vegades dins d'una mostra (dins de l'array).

Calculem la única moda d'un array.

# create NumPy array of values with only one mode
x = np.array([2, 2, 2, 3, 4, 4, 5, 5, 5, 5, 7])

#find unique values in array along with their counts
vals, counts = np.unique(x, return_counts=True)
# print("Valors        ",vals)
# print("Núm.Vegades   ",counts)

# Numero vegades que es repeteix la moda.
num_oc = counts.max()

# Posició on es troba la moda.
id_moda = list(counts).index(num_oc)

# Valor de la moda, per fi
valor_moda = vals[id_moda]
print('Valor moda        ',valor_moda)
print('Núm. repeticions  ',num_oc)

Com podeu veure, no és tant senzill com els altres càlculs; però el mètode unique ens ha facilitat una mica les coses.

Si simplement ens demanen representar de forma còmode les ocurrències de cada valor, les podem aconseguir creant un nou array a partir de la operació unique:

np.array(np.unique(x, return_counts=True)).T
    array([[ 1,  5],
           [ 2,  3],
           [ 5,  1],
           [25,  1]])

I què passa si tenim més d'una moda ??

Tenim diverses opcions, com un algorisme més llarg, o bé usar una altra llibreria bastant habitual scipy que ens ajudi amb un mètode mode. En aquests articles es plantegen les dues solucions.

  • https://statisticsglobe.com/mode-numpy-array-python
  • https://www.statology.org/numpy-mode/

Preguntes i exercicis d'operacions i estadístiques amb matrius.

Pr5. Explica amb les teves paraules la principal diferència entre els mètodes view i copy.

Pr6. Què fa aquest codi ? Només una opció és correcta.

import numpy as np
a = np.array([[9, 3],
       [5, 10],
       [7, 6]])
print(a.mean(axis=0))
  1. Calcula la mitjana aritmètica de tots els números de la matriu, i retorna un únic resultat.
  2. Calcula la mitjana aritmètica de les files de la matriu, i retorna 2 resultats.
  3. Calcula la mitjana aritmètica de les columnes de la matriu, i retorna 3 resultats.
  4. Cap de les respostes és correcta.

Ex3. Generar i tractar arrays aleatoris: un amb les edats de diversos alumnes (que poden ser entre 16 i 30) i un altre amb un dels 8 grups sanguinis ("A+","A-"...). Posteriorment, afegeix manualment a les matrius 3 alumnes majors de 30 (edats i grup sanguini). Finalment, crea dos nous arrays (còpies exactes) que continguin només els alumnes majors d'edat.

Ex4. Operacions i estadístiques Tenim les qualificacions dels examens de 3 alumnes en 2 mòduls (assignatures) del trimestre 1. I un altre array de les mateixes dimensions del trimestre 2 i final (decimals)

import numpy as np
# Qualificacions dels alumnes en dos trimestres
trimestre1 = np.array([
    [7.5, 8.0],
    [6.0, 7.5],
    [8.5, 9.0]
])
trimestre2 = np.array([
    [8.0, 5.5],
    [4.0, 8.0],
    [9.0, 9.5]
])

Realitza les següents:

  • Suma valors dels 2 arrays i guarda la suma en un nou array.
  • Multiplica per 1.1 els valors de la última filera.
  • Divideix per 2 els valors de l'array.
  • Calcula la mitjana aritmètica de notes del mòdul que està a la primera posició.

Pr6. Resposta: 2.

Ex3

import numpy as np

grups_sanguinis = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]

# Generar un array aleatori amb edats entre 16 i 30
num_alumnes = 10
edats = np.random.randint(16, 31, num_alumnes)

# Generar un array aleatori amb grups sanguinis
grups = np.random.choice(grups_sanguinis, num_alumnes)

# Afegir manualment 3 alumnes majors de 30
edats_extens = np.append(edats, [35, 40, 45])
grups_extens = np.append(grups, ["A+", "B-", "O+"])

print("Array d'edats amb alumnes majors de 30:")
print(edats_extens)
print("\nArray de grups sanguinis amb alumnes majors de 30:")
print(grups_extens)

edats_majors16

Ex4

# Sumar valors dels dos arrays i guardar la suma en un nou array
suma_trimestres = trimestre1 + trimestre2

print("Suma dels dos trimestres:")
print(suma_trimestres)

# Multiplicar per 1.1 els valors de la última filera
suma_trimestres[-1] *= 1.1

print("\nSuma amb última filera multiplicada per 1.1:")
print(suma_trimestres)

# Dividir per 2 els valors de l'array
divisio_trimestres = suma_trimestres / 2

print("\nArray dividit per 2:")
print(divisio_trimestres)

# Calcular la mitjana aritmètica de les notes del mòdul que està a la primera posició (columna 0)
mitjana_modul1 = np.mean(divisio_trimestres[:, 0])

print("\nMitjana aritmètica de les notes del mòdul 1:")
print(mitjana_modul1)

# Filtrar alumnes majors d'edat (18 anys o més)
adults_mask = edats_extens >= 18
edats_adults = edats_extens[adults_mask]
grups_adults = np.array(grups_extens)[adults_mask]

print("\nArray d'edats només amb alumnes majors d'edat:")
print(edats_adults)
print("\nArray de grups sanguinis només amb alumnes majors d'edat:")
print(grups_adults)

Exemple resolt quadres Punnet.

En el context dels gats i el pelatge, assumim que l'al·lel per al pelatge negre és dominant (P) i l'al·lel per al pelatge blanc és recessiu (p).

Després, tenim que l'al·lel de la cua llarga és C i dominant i el de cua curta la c.

Els gamets possibles del creuament d'un gat i una gata heterozigots pel gen del pelatge i pel de la cua són:

  • Gat (PpCc): PC, Pc, pC, pc
  • Gata (PpCc): PC, Pc, pC, pc

Dibuixa el quadre de Punnet 4 x 4 resultant.

Quan el tinguis, respon i demostra a Python les probabilitats que el descendent tingui cua curta i pèl llarg.

Khan Academy

import numpy as np

allels = ['PC', 'Pc', 'cP', 'pc']

punnett_square = np.empty((len(allels), len(allels)), dtype='U4')

# Llenar el cuadro de Punnett con las combinaciones de alelos
for i in range(len(allels)):
    for j in range(len(allels)):
        punnett_square[i, j] = allels[i][0] + allels[j][0] + allels[i][1] + allels[j][1]

print(punnett_square)

# Vols saber les probabilitats de llarga i pel curt.
elements = ['Ppcc', 'PPcc', 'pPcc']

# Aplano la matriu per fer el càlcul més senzill.
flat_punnett = punnett_square.flatten()
# Comptar quantes vegades apareix cada element a la matriu
counts = [np.sum(punnett_square == element) for element in elements]

print(f"Probabilitats = {np.sum(counts)/len(flat_punnett)}\n")

Ara practica tu, crea una variant d'aquest problema, o crea'n un altre.


Com accedir al docstring per obtenir ajuda?

Esta sección cubre help(), ?, ??

La biblioteca Numpy, com qualsevol bona feina creada en Python, inclou documentació d'ajuda en format docstring. En la majoria de casos, aquest docstring conté un resum de l'objecte i com utilitzar-lo. Python té la funció help() incorporada per a accedir a aquesta informació. Per exemple:

help(max)
Help on built-in function max in module builtins:

max(...)
    max(iterable, *[, default=obj, key=func]) -> value
    max(arg1, arg2, *args, *[, key=func]) -> value

    With a single iterable argument, return its biggest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the largest argument.

Debido a que el acceso a información adicional es tan útil, IPython utiliza el ?carácter de abreviatura para acceder a esta documentación junto con otros información pertinente. IPython es una cáscara de comando para la computación interactiva en múltiples idiomas. Puede encontrar más información sobre IPython aquí.

Aquesta notació també t'anirà bé per obtenir els mètodes que pots aplicar dins de l'objecte que has creat i altres objectes que conté.

Per exemple, si tens aquesta matriu:

a = np.array([1, 2, 3, 4, 5, 6])

I poses el mètode a? et surt:

a?
Type:            ndarray
String form:     [1 2 3 4 5 6]
Length:          6
File:            ~/anaconda3/lib/python3.9/site-packages/numpy/__init__.py
Docstring:       <no docstring>
Class docstring:
ndarray(shape, dtype=float, buffer=None, offset=0,
        strides=None, order=None)

Guardar y recuperar matrius NumPy en fitxers

La secció descriu: np.save, np.savez, np.savetxt, np.load, np.loadtxt

És important guarda i carregar seves matrius al disc sense haver de tornar a executar el codi. Afortunadament, hi ha diverses maneres de salvar i carregar objectes amb NumPy. Els objectes ndarray es poden guardar i carregar dels fitxers de disc amb loadtxt i savetxt, si són fitxers de text pla; load i save si són funcions que manejen fitxers binaris de NumPy.

Els fitxers .npy i .npz emmagatzemen dades, forma, dtype i altra informació requerida per reconstruir el ndarray d'una manera que permeti que la matriu sigui recuperat correctament, fins i tot quan el fitxer està en una altra màquina amb diferent arquitectura.

Si voleu emmagatzemar un sol objecte ndarray, utilitzeu-lo com un fitxer .npy usant np.save. Si voleu emmagatzemar més d'un objecte ndarray en un sol fitxer, deseu-lo com a fitxer .npz usant np.savez. També podeu desar diversos arrays en un sol fitxer en format npz comprimit amb savez_compressed.

És fàcil de guardar i carregar i array amb np.save() i np.load()

a = np.array([1, 2, 3, 4, 5, 6])
np.save('filename', a)
b = np.load('filename.npy')
print(b)
[1 2 3 4 5 6]

Pots guardar una matriz de NumPy com un arxiu de text pla amb un archiu .csv o .txt amb np.savetxt i recuperar-lo amb np.loadtxt

csv_arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
np.savetxt('new_file.csv', csv_arr)
np.loadtxt('new_file.csv')
array([1., 2., 3., 4., 5., 6., 7., 8.])

Aquestes funcions accepten paràmetres opcionals com:

  • delimitador (pot ser un símbol com el ; la , el tabulador o un altre)
  • header primera linia del fitxer, que no es posarà a l'array
  • usecols et permet dir quines columnes vols llegir.

Importación y exportación de un CSV amb Pandas.

Es fàcil de llegir un CSV que conté informació rellevant que volguem inserir en un array (que sigui homogènia, és clar)

La forma més fácil i convenient de fer-ho és amb la llibreria Pandas.

Si ja tens ganes de veure Pandas en produnditat, accedeix a la nostra guia de Pandas

Si vols provar inmediatament el codi, no oblidis d'instal·lar pandas amb pip:

$ pip install pandas

Un cop fet, ja pots crear el teu fitxer .py i cercar (o crear) un fitxer en format CSV per provar:

Billie Holiday;Jazz;1300000;27000000
Jimmie Hendrix;Rock;2700000;70000000
Miles;Davis;Jazz;1500000;48000000
SIA;Pop;2000000;74000000
import pandas as pd
# If all of your columns are the same type:
x = pd.read_csv('music.csv', header=0).values
print(x)
[['Billie Holiday' 'Jazz' 1300000 27000000]
 ['Jimmie Hendrix' 'Rock' 2700000 70000000]
 ['Miles Davis' 'Jazz' 1500000 48000000]
 ['SIA' 'Pop' 2000000 74000000]]

Si en comptes de tot el fitxer, només vols seleccionar algunes columnes ho pots fer amb el paràmetre usecols.

x = pd.read_csv('music.csv', usecols=['Artist', 'Plays']).values
print(x)
[['Billie Holiday' 27000000]
 ['Jimmie Hendrix' 70000000]
 ['Miles Davis' 48000000]
 ['SIA' 74000000]]

És fàcil d'usar Pandas per tal d'exportar la matriu. Ho pots fer transformant la matriu a DataFrame, la estructura de Pandas per gestionar dades tabulars.

Un cop fet, pots usar el mètode de Pandas to_csv

a = np.array([[-2.58289208,  0.43014843, -1.24082018, 1.59572603],
              [ 0.99027828, 1.17150989,  0.94125714, -0.14692469],
              [ 0.76989341,  0.81299683, -0.95068423, 0.11769564],
              [ 0.20484034,  0.34784527,  1.96979195, 0.51992837]])
df = pd.DataFrame(a)
print(df)
df.to_csv('pd.csv')
          0         1         2         3
0 -2.582892  0.430148 -1.240820  1.595726
1  0.990278  1.171510  0.941257 -0.146925
2  0.769893  0.812997 -0.950684  0.117696
3  0.204840  0.347845  1.969792  0.519928

Pots recuerar el contingut del fitxer:

data = pd.read_csv('pd.csv')

L'altra opció és guardar la matriu dirèctament:

np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header='1,  2,  3,  4')

Si estàs usant la línea de comandes de Linux, pots llegir el teu CSV guardat en sense obrir-lo amb la comanda cat:

$ cat np.csv
---  1,  2,  3,  4
-2.58,0.43,-1.24,1.60
0.99,1.17,0.94,-0.15
0.77,0.81,-0.95,0.12
0.20,0.35,1.97,0.52

O pots obrir-lo amb qualsevol editor.

Exercici lectura fitxer CSV.

Ex7. Lectura fitxer CSV de temperatures

Importa en un array bidimensional de Numpy un fitxer CSV de temperatures mensuals de Barcelona els últims 10 anys.

Després de tractar-lo, el fitxer CSV té el següent contingut:

Any,Temp_Mitjana_Gener,Temp_Mitjana_Febrer,Temp_Mitjana_Marc,Temp_Mitjana_Abril,Temp_Mitjana_Maig,Temp_Mitjana_Juny,Temp_Mitjana_Juliol,Temp_Mitjana_Agost,Temp_Mitjana_Setembre,Temp_Mitjana_Octubre,Temp_Mitjana_Novembre,Temp_Mitjana_Desembre
2014,9.8,10.1,12.3,15.3,16.2,21.7,22.6,23,22,19.6,13.6,9.1
2015,9.1,8.2,12,14.8,19.1,23.2,26.0,23.50,19.7,16.7,14.3,12.6
2016,10.7,11.3,11.1,13.6,16.4,21.6,24.9,24.5,22.3,17.1,12.7,11.5
2017,7.9,11.4,13.3,14.2,18.3,23.6,24.2,24.5,19.5,18.6,12.5,8.5
2018,10.5,6.7,10.8,14.7,17.1,21.5,25.3,25.8,22.5,17.0,12.4,11.1
2019,8.1,11.9,13.5,13.4,15.6,21.9,25.4,25.1,21.8,18.5,11.9,11.2
2020,10.0,12.8,11.9,14.3,19.4,20.1,25.0,25.5,21.7,16.4,14.7,9.3
2021,7.7,11.6,12.1,12.9,17.3,23.3,24.8,24.5,23.0,18.1,11.3,10.9
2022,10.2,11.8,10.8,14.1,20.7,24.7,26.7,27.2,22.5,20.7,15.2,12.6
2023,9.2,10.3,14.1,16.1,18.1,23.4,25.5,26,23.2,20.2,14.8,12.1

El pots trobar dins de la nostra web també:

temperaturesbcn_2014_2023.csv

Realitza 3 operacions estadístiques agrupades que consideris rellevants sobre l'array.

Curiositat: El contingut original del fitxer CSV de temperatures l'hem obtingut de portal de dades obertes de l'Observatori Fabra i Puig de Barcelona, que recopila les temperatures des del 1780.

Solució Ex7, lectura CSV

Per llegir el fitxer CSV i posar-lo en un array de Numpy, ometent la primera columna i la primera fila, podem utilitzar la funció np.loadtxt amb els paràmetres adequats per saltar la primera fila i ometre la primera columna.

Aquest codi fa les següents operacions:

  • Utilitza np.genfromtxt per llegir el fitxer CSV.
  • El paràmetre delimiter=',' especifica que els valors estan separats per comes.
  • El paràmetre skip_header=1 fa que es salti la primera fila del CSV (les capçaleres).
  • El paràmetre usecols=range(1, 13) fa que es llegeixin només les columnes de la 1 a la 12 (les temperatures de Gener a Desembre, ometent la primera columna que és l'any).
  • Per utilitzar aquest codi, assegura't que el fitxer temperatures.csv estigui en el mateix directori des del qual estàs executant el codi o proporciona el camí complet al fitxer.
import numpy as np

# Llegir el fitxer CSV i ometre la primera fila i primera columna
data = np.loadtxt('temperaturesbcn_2014_2023.csv', delimiter=',', skiprows=1, usecols=range(1, 13))

mitjana_per_any = np.mean(data, axis=1)
print("Mitjana de les temperatures mensuals per any:")
print(mitjana_per_any)

max_per_any = np.max(data, axis=1)
min_per_any = np.min(data, axis=1)
print("\nTemperatura màxima per any:")
print(max_per_any)
print("\nTemperatura mínima per any:")
print(min_per_any)

mitjana_per_mes = np.mean(data, axis=0)
print("\nMitjana de la temperatura de cada mes al llarg dels anys:")
print(mitjana_per_mes)