Tabele so lahko tudi večdimenzionalne. "Več-" naj bo, za začetek, kar "dvo-". (Ker je dve ravno število dimenzij, ki jih imajo sodobni zasloni in projektorji, se dvodimenzionalne tabele ravno še dobro vidi. Za tridimenzionalne bi potrebovali posebna očala, za štiri pa drugačno vesolje.)
import numpy as np
= np.array([[2, -7, 2], [5, 9, 1], [1, -0, 0], [-1, -2, -8]]) a
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
V Pythonu bi bil to seznam seznamov. Do tretje vrstice bi prišli z
3] a[
array([-1, -2, -8])
in do drugega elementa v njej z
3][2] a[
np.int64(-8)
Numpyjeve tabele lahko indeksiramo z več indeksi v istih oklepajih.
Namesto a[3][2]
lahko pišemo
3][2] a[
np.int64(-8)
Za oba indeksa veljajo enake čarovnije kot za Pythonove indekse sicer. Tako lahko dobimo vse vrstice od druge do četrte in stolpce do zadnjega:
2:4, :-1] a[
array([[ 1, 0],
[-1, -2]])
Ali pa, recimo, vse vrstice drugega stolpca - z drugimi besedami, drugi stolpec.
2] a[:,
array([ 2, 1, 0, -8])
Ker je stolpec enodimenzionalna reč, ga je Python pač prevrnil v enodimenzionalno tabelo.
Ko smo ravno pri prevračanju, omenimo še splošno prevračanje: tabela
ima "metodo" T
, ki vrne transponirano tabelo. Narekovaji so
potrebni, ker T
v resnici ni metoda temveč nekaj drugega, o
čemer se pri predmetu niti slučajno ne bomo učili. T
-ja ni
potrebno poklicati. Malo zato, ker bi oklepaji vzeli več prostora kot
samo ime funkcije, malo zato, ker T prihaja iz matematike, kjer bi
transponirano matriko $\matrix{A}$
zapisali kot $\matrix{A}^T$.
Če je torej a
takšna
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
je a.T
takšna:
a.T
array([[ 2, 5, 1, -1],
[-7, 9, 0, -2],
[ 2, 1, 0, -8]])
Stolpci so postali vrstice, vrstice stolpci. Tabelo bomo presenetljivo pogosto takole zasukali, saj bo določene reči lažje narediti z vrsticami, kot bi jih bilo s stolpci.
Tridimenzionalne tabele se vedejo podobno kot dvodimenzionalne. Imajo
pač en indeks več, pa izpišejo se bolj nerodno. In štiri- ali
petdimenzionalne enako. .T
pa obrne vrstni red dimenzij.
(Vendar ga pri takih tabelah vsaj jaz še nisem uporabil. No, pa take
tabele tudi.)
Tabele smo doslej sestavljali tako, da smo poklicali
np.array
in podali pripravljen seznam (ali seznam seznamov,
če smo želeli dve dimenziji) v Pythonu. (Prejšnji teden pa tudi tako, da
smo poklicali np.genfromtxt
, ki je prebral datoteko v
tabelo, še več tabel pa smo dobili z raznimi načini indeksiranja tako
prebrane tabele.)
Včasih si moramo pripraviti prazno tabelo - tabelo ničel, enic ali česa drugega. Kot argument podamo terko z dimenzijami. Če terka vsebuje dva elementa, bo tabela dvodimenzionalna. Če pet, bo petdimenzionalna.
2, 4)) np.zeros((
array([[0., 0., 0., 0.],
[0., 0., 0., 0.]])
2, 4)) np.ones((
array([[1., 1., 1., 1.],
[1., 1., 1., 1.]])
2, 4), 42) np.full((
array([[42, 42, 42, 42],
[42, 42, 42, 42]])
Pogosto potrebujemo tudi tabelo zaporednih števil.
12) np.arange(
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
Ali, recimo, 15 enako razmaknjenih števil med 0 in 3.5.
0, 3.5, 15) np.linspace(
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. , 2.25, 2.5 ,
2.75, 3. , 3.25, 3.5 ])
Mimogrede spoznajmo še en način za ustvarjanje tabele: pripravimo tabelo s petimi vrsticami in tremi stolpci naključnih števil med -1 in 1.
= np.random.uniform(-1, 1, (5, 2))
r
r
array([[-0.25293564, -0.94072709],
[ 0.35411039, 0.1389441 ],
[-0.89286703, -0.55631392],
[-0.68174291, 0.26498722],
[-0.72935741, 0.51457491]])
V gornjih tabelah so bili int
-i ali
float
-i; kdaj se je zgodilo kaj, vidimo iz izpisa. Če
sestavimo tabelo iz seznama ali z np.full
, je tip odvisen
od argumenta. Če je v seznamu kak float
, bo to tabela
float
-ov, če sami int
-i, tabela
int
-ov.
Seveda pa
numpy
ne pozna samo enega int
-a in enega
float
-a.Čas za nov mednaslov. :)
Spomnimo se na a
.
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
a.ndim
2
a.shape
(4, 3)
a.size
12
a.dtype
dtype('int64')
a.itemsize
8
ndim
je povedal, da je tabela dvodimenzionalna,
shape
pove njeno obliko (dolžina shape
je
ravno ndim
) in size
pove število elementov
(kar je ravno produkt tega, kar imamo v shape
). Če tako
pogledamo, bi bilo dovolj imeti shape
, saj je možno vse
drugo izračunati iz njega. Da, vendar size
včasih
potrebujemo in ndim
včasih preverimo, zato je čisto
praktično, da obstajata.
Bolj zanimivi sta zadnji stvari. dtype
pove podatkovni
tip elementov. Numpy ima svoje podatkovne tipe, tako številske kot ...
druge. Elementi tabele a
so tipa int64
, kar
pomeni int
, shranjen s 64 biti. itemsize
je
število bajtov, ki jih zasede posamezni element. Ker je vsaj bajt
sestavljen iz 8 bitov (kar se zdi danes logično, zgodovina računalništva
pa beleži tudi drugačne modele), je 64 bitov 8 bajtov.
Zakaj ravno int64
? Zakaj ne int40
? (Ker ne
obstaja. :) Zakaj ne int32
? V osnovi so v tabeli
int
-i, katera različica (širina)
int
-a bo privzeta, pa je odvisno od, hm, operacijskega
sistema oziroma vedenja numpy
-ja na posameznem operacijskem
sistemu. Na MS Windows so bili privzeti int
-i še nedavno
(ali pa še vedno?) 32-bitni, četudi je operacijski sistem že davno
64-biten. Na macOS in Linux so 64-bitni. (Kako vem? Ker nam je že
povzročalo veselje pri programiranju programov, ki bi morali delovati
tudi na MS Windows, vendar so skrivnostno crkovali.)
Najboljše je, da se glede tega ne vznemirjate preveč, saj bo za vaše
potrebe dovolj, da se delate, da so tabele bodisi int
ali
float
.
Kakšnega tipa bo tabela, je odvisno od tega, kaj zapišemo vanjo.
2, 8, -1]).dtype np.array([
dtype('int64')
2, 1, 3.14, 8]).dtype np.array([
dtype('float64')
Tabele, ki jih sestavimo z zeros
in ones
so
praviloma float
, razen če zahtevamo drugače. Prav tako
lahko eksplicitno zahtevamo drugačen tip, kadar tabelo sestavimo z
np.array
ali katero drugo funkcijo.
4) np.ones(
array([1., 1., 1., 1.])
4, dtype=int) np.ones(
array([1, 1, 1, 1])
4, dtype=float) np.ones(
array([1., 1., 1., 1.])
4, dtype=bool) np.ones(
array([ True, True, True, True])
1, 2, 3], dtype=float) np.array([
array([1., 2., 3.])
Spremenimo lahko obliko tabele ali njen tip. Točneje: iz tabele lahko naredimo drugo tabelo, ki ima drugačno obliko ali tip elementov.
Obliko spreminjamo z reshape
.
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
= a.reshape(2, 6)
b
b
array([[ 2, -7, 2, 5, 9, 1],
[ 1, 0, 0, -1, -2, -8]])
In tu se začne numpy-jeva magija. b
je le drugačen
pogled na pomnilnik, v katerem je shranjen a
. Podatki se tu
niso kopirali, torej nismo izgubljali ne časa ne dodatnega pomnilnika. A
to za nas ni posebej pomembno - vsaj dokler te tabele ne spreminjamo.
Spreminjanje b
-ja bi namreč spremenilo tudi
a
.
Ko obliko tabele, mora imeti nova tabela enako število elementov. Tabelo 3x4 lahko spremenimo v 2x6 ali celo 1x12, ne pa v 3x5.
Eno od dimenzij lahko nastavimo na -1
pa bo numpy sam
izračunal, kakšna mora biti. Če hočemo spraviti a
v dva
stolpca in se nam danes ne da računati, koliko je 3 x 4 / 2, napišemo
kar
-1, 2) a.reshape(
array([[ 2, -7],
[ 2, 5],
[ 9, 1],
[ 1, 0],
[ 0, -1],
[-2, -8]])
a
se pri tem seveda ni spremenil. To je nova tabela (oz.
nov pogled na obstoječo tabelo).
Tip spreminjamo z astype
. Tu dobimo novo tabelo.
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
float) a.astype(
array([[ 2., -7., 2.],
[ 5., 9., 1.],
[ 1., 0., 0.],
[-1., -2., -8.]])
bool) a.astype(
array([[ True, True, True],
[ True, True, True],
[ True, False, False],
[ True, True, True]])
"U5") a.astype(
array([['2', '-7', '2'],
['5', '9', '1'],
['1', '0', '0'],
['-1', '-2', '-8']], dtype='<U5')
Ups, kakšen tip je U5
? Niz (U kot Unicode) z največ
petimi znaki.
Dovolj naštevanja, čas je za primer.
Najprej preberimo podatke.
= np.genfromtxt(
zapisnik "../domace-naloge/03-drazba-brez-anonimnosti/zapisnik.txt",
=",") delimiter
Funkciji np.getfromtxt
lahko podamo bodisi ime datoteke
bodisi odprto datoteko. Slednje je posebej praktično, če smo na Windows
in moramo še določati encoding
.
= np.genfromtxt(
zapisnik open("../domace-naloge/03-drazba-brez-anonimnosti/zapisnik.txt",
="utf-8"),
encoding=",") delimiter
Začetek tabele je tak.
5] zapisnik[:
array([[nan, nan, 31.],
[nan, nan, 33.],
[nan, nan, 35.],
[nan, nan, 37.],
[nan, nan, 40.]])
nan
? nan
pomeni not a number. Da,
seveda, prva dva stolpca vsebujeta imena predmetov in ljudi, not a
number.
Določimo, naj bo dtype
niz.
= np.genfromtxt(
zapisnik "../domace-naloge/03-drazba-brez-anonimnosti/zapisnik.txt",
=",",
delimiter=str)
dtype
5] zapisnik[:
array([['slika', 'Berta', '31'],
['slika', 'Ana', '33'],
['slika', 'Berta', '35'],
['slika', 'Fanči', '37'],
['slika', 'Ana', '40']], dtype='<U21')
(Preden nadaljujemo: numpy pravi dtype='U12'
. To pomeni
niz z največ 20 znaki.)
Pa imamo dvodimenzionalno tabelo. Vendar bi bilo pravzaprav bolj
praktično imeti tri spremenljivke, predmeti
,
osebe
in cene
, vsaka bi imela svoj
stolpec.
Lahko bi se šli
= zapisnik[:, 0]
predmeti = zapisnik[:, 1]
osebe = zapisnik[:, 2] cene
vendar obstaja prikladen trik: tabelo s tremi stolpci in bogvekoliko vrsticami prevrnimo v tabelo s tremi vrsticami in bogveliko stolpci.
5] zapisnik.T[:, :
array([['slika', 'slika', 'slika', 'slika', 'slika'],
['Berta', 'Ana', 'Berta', 'Fanči', 'Ana'],
['31', '33', '35', '37', '40']], dtype='<U21')
Da ne bi bilo predolgo, smo, tako kot prej pet vrstic, zdaj izpisali
le pet stolpcev. (Poglej kako! Prvi indeks je :
, da dobimo
vse tri vrstice, drugi :5
, da dobimo le prvih pet
stolpcev.
V tako prevrnjeni tabeli je zapisnik.T[0]
prva vrstica,
zapisnik.T[1]
druga in zapisnik.T[2]
tretja.
To je imenitno, ker lahko te tri vrstice preprosto razpakiramo v tri
spremenljivke!
= zapisnik.T predmeti, osebe, cene
15] predmeti[:
array(['slika', 'slika', 'slika', 'slika', 'slika', 'slika',
'pozlačen dežnik', 'Meldrumove vaze', 'Meldrumove vaze',
'Meldrumove vaze', 'Meldrumove vaze', 'Meldrumove vaze',
'Meldrumove vaze', 'Meldrumove vaze', 'Meldrumove vaze'],
dtype='<U21')
15] osebe[:
array(['Berta', 'Ana', 'Berta', 'Fanči', 'Ana', 'Fanči', 'Ema', 'Greta',
'Ana', 'Greta', 'Ana', 'Fanči', 'Ana', 'Greta', 'Ana'],
dtype='<U21')
15] cene[:
array(['31', '33', '35', '37', '40', '45', '29', '44', '46', '48', '53',
'57', '60', '61', '63'], dtype='<U21')
Aha, cene so nizi, potrebno jih bo spremeniti v številke. Zamahnemo s čarobno paličko numpyja, pa je.
= cene.astype(int) cene
cene
array([ 31, 33, 35, 37, 40, 45, 29, 44, 46, 48, 53, 57, 60,
61, 63, 67, 71, 76, 78, 50, 55, 60, 61, 62, 65, 68,
70, 74, 76, 80, 83, 30, 32, 37, 39, 43, 44, 45, 50,
53, 55, 58, 61, 63, 68, 72, 76, 77, 81, 85, 86, 90,
92, 94, 97, 98, 99, 100, 103, 107, 15, 27, 30, 35, 39,
40, 45, 47, 49, 53, 55, 58, 59, 62, 63, 16, 21])
Zdaj pa le po domači nalogi.
Najvišjo ceno je trivialno najti,
max(cene) np.
np.int64(107)
Nas pa zanima tudi, za kateri predmet gre in kdo ga je kupil. Zanima
nas torej, kaj je v tabelah predmeti
in osebe
na tistem mestu, kjer je v cene
številka 107. Torej ne
potrebujemo (ali pa vsaj: ne potrebujemo samo) največjega števila v
cene
temveč tudi (in predvsem) njegov indeks.
Pomnite li, kako smo pisali funkcijo argmax
? Python je
nima, Numpy pač.
np.argmax(cene)
np.int64(59)
Potem pa ni problema:
= np.argmax(cene)
naj_i
print(f"Najdražji predmet, {predmeti[naj_i]}, je za {cene[naj_i]} kupila {osebe[naj_i]}.")
Najdražji predmet, kip, je za 107 kupila Dani.
Prejšnji teden je bilo preprosto: imeli smo le seznam cen in cene
različnih predmetov so bile razmejene z -1
. Zdaj imamo
seznam predmetov in ugotoviti je treba, v katerih vrsticah se predmet
zamenja.
Trik poznamo že od prejšnjič: zadnjič smo računali razlike med zaporednimi vrsticami (ko nas je zanimalo, za koliko se kolesar dvigne ali spusti med dvema zaporednima meritvama). Zdaj nas zanima preprosto, ali sta dve zaporedni vrstici različni.
1:] != predmeti[:-1] predmeti[
array([False, False, False, False, False, True, True, False, False,
False, False, False, False, False, False, False, False, False,
True, False, False, False, False, False, False, False, False,
False, False, False, True, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False, True, True, False, False,
False, False, False, False, False, False, False, False, False,
False, False, True, False])
Poiščimo indekse, kjer pride do sprememb.
= np.flatnonzero(predmeti[1:] != predmeti[:-1])
spremembe
spremembe
array([ 5, 6, 18, 30, 59, 60, 74])
Ker smo izpustili prvo vrstico, so tole v bistvu indekse
zadnjih vrstic vsakega predmeta. Manjka le zadnji indeks; ta je
pač len(predmeti) - 1
.
Če k spremembam prištejemo 1
, pa dobimo prve
vrstice za vsak predmet. Zdaj nam zmanjka prva vrstica prvega predmeta;
te ja preprosto 0
.
Pripravimo tabeli z indeksi začetnih (ki jih bomo potrebovali
kasneje) in končnih (ker jih bomo potrebovali zdaj) vrstic, ki opisujejo
posamezni predmet. Manjkajoči prvi in zadnji element pripnemo z
np.hstack
. Spet ne spreglejmo: hstack
kot
argument pričakuje terko s tabelami, ki naj jih spne, zato dvojni
oklepaji.
= np.hstack(([0], spremembe + 1))
zacetni = np.hstack((spremembe, [len(predmeti) - 1])) koncni
Zdaj, ko imamo končne indekse, vemo, da so končne cene
cene[koncni]
array([ 45, 29, 78, 83, 107, 15, 63, 21])
Pripadajoči predmeti pa
predmeti[koncni]
array(['slika', 'pozlačen dežnik', 'Meldrumove vaze', 'skodelice', 'kip',
'čajnik', 'srebrn jedilni servis', 'perzijska preproga'],
dtype='<U21')
Tako lahko izpišemo:
for predmet, cena in zip(predmeti[koncni], cene[koncni]):
print(f"{predmet:25} {cena:3}")
slika 45
pozlačen dežnik 29
Meldrumove vaze 78
skodelice 83
kip 107
čajnik 15
srebrn jedilni servis 63
perzijska preproga 21
Seveda bi lahko poprej pripravili tabelo končnih predmetov in cen.
= predmeti[koncni]
konpred = cene[koncni] koncene
To malo poenostavi zip
:
for predmet, cena in zip(predmeti[koncni], cene[koncni]):
print(f"{predmet:25} {cena:3}")
slika 45
pozlačen dežnik 29
Meldrumove vaze 78
skodelice 83
kip 107
čajnik 15
srebrn jedilni servis 63
perzijska preproga 21
Kaj pa, če bi želeli te predmete urediti po cenah?
Urediti cene - znamo:
np.sort(koncene)
array([ 15, 21, 29, 45, 63, 78, 83, 107])
Vendar je problem, da bi zdaj radi preuredili imena predmetov enako,
kot so tu preurejene cene. Se pravi, morali bi, hm, poznati indekse, ki
preslikajo te številke iz originalne tabele, koncene
v
takole preurejeno tabelo, da bi potem enako preslikali imena
predmetov.
Ne razumemo gornjega stavka? Ne? OK, gremo počasi.
Pomnite li imenitno našo funkcijo argmax
, ki namesto
največjega elementa vrne njegov indeks. Enako - in še bolj - imeniten je
argsort
, ki namesto urejene tabele vrne indekse, ki uredijo
tabelo.
= np.argsort(koncene) red
red
array([5, 7, 1, 0, 6, 2, 3, 4])
Primerjajmo to s
koncene
array([ 45, 29, 78, 83, 107, 15, 63, 21])
red
nam pove tole: če hočeš seznam z urejenimi elementi
koncene
, moraš najprej vzeti element z indeksom
5
,
5] koncene[
np.int64(15)
nato tistega z indeksom 7,
7] koncene[
np.int64(21)
potem onega z indeksom 1,
1] koncene[
np.int64(29)
... ali, če skrajšamo zgodbo tako, da se spomnimo, da lahko tabelo indeksiramo z indeksi iz druge tabele:
koncene[red]
array([ 15, 21, 29, 45, 63, 78, 83, 107])
Ker so predmeti v konpred
našteti tako, da jim pripadajo
istoležne cene, moramo po enakem vrstnem redu pobrati še predmete.
konpred[red]
array(['čajnik', 'perzijska preproga', 'pozlačen dežnik', 'slika',
'srebrn jedilni servis', 'Meldrumove vaze', 'skodelice', 'kip'],
dtype='<U21')
Če želimo urediti padajočo, pač obrnemo vrstni red
.
Pa imamo:
= np.argsort(koncene)[::-1]
red
for predmet, cena in zip(konpred[red], koncene[red]):
print(f"{predmet:25} {cena:3}")
kip 107
skodelice 83
Meldrumove vaze 78
srebrn jedilni servis 63
slika 45
pozlačen dežnik 29
perzijska preproga 21
čajnik 15
K nalogi dodajmo še, da morajo biti predmeti izpisani po padajočem številu ponudb.
Če vemo, da so prve vrstice, ki se nanašajo na določen predmet v
koncni
, začetne pa v zacetni
,
print(koncni)
print(zacetni)
[ 5 6 18 30 59 60 74 76]
[ 0 6 7 19 31 60 61 75]
nam je le odšteti tidve tabeli in prišteti 1, pa imamo število ponudb za vsak predmet. Število ponudb arguredimo in potem nadaljujemo tako kot prej.
= koncni - zacetni + 1
ponudb
= np.argsort(ponudb)[::-1]
red
for predmet, pon in zip(konpred[red], ponudb[red]):
print(f"{predmet:25} {pon:3}")
kip 29
srebrn jedilni servis 14
skodelice 12
Meldrumove vaze 12
slika 6
perzijska preproga 2
čajnik 1
pozlačen dežnik 1
Tole smo skoraj že naredili, pa še malo več. Le tisti del o izpisu več predmetov, ki si deli prvo mesto bi lahko dodali. Pa bomo zdaj naredili drugače: poiskali bomo predmet z največ ponudbami in izpisali vse, ki imajo toliko ponudb. To bo sicer le eden, a iz programa bo očitno, da bi jih lahko bilo več.
= koncni - zacetni + 1
ponudbe
= ponudbe == np.max(ponudbe) maska
Zdaj vsebuje maska
True
za vse elemente, ki
imajo toliko ponudb, kolikor je np.max(ponudbe)
.
ponudbe
array([ 6, 1, 12, 12, 29, 1, 14, 2])
maska
array([False, False, False, False, True, False, False, False])
Sicer je True
le eden (tam, kjer je 29), lahko pa bi jih
bilo tudi več.
Zdaj izpišimo te predmete: masko, ki smo jo sestavili iz
ponudbe
, uporabimo na predmeti
.
for predmet in konpred[maska]:
print(predmet)
kip
Nazaj k dvodimenzionalni matriki a
.
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
Kaj vrnejo funkcije, kot sta np.max
in
np.sum
?
max(a) np.
np.int64(9)
sum(a) np.
np.int64(2)
To je včasih v resnici uporabno, vendar redko. Pogosteje želimo vsoto po vrsticah ali po stolpcih.
Matrika ima dve osi. Prva gre dol, po vrsticah (pač: prvi indeks so
vrstice), druga po stolpcih (ker: drugi indeks). Funkcije
np.max
, np.sum
in druge, kjer je to smiselno
(pri argsort
, recimo, ni) sprejmejo dodatni argument
axis
.
max(a, axis=0) np.
array([5, 9, 2])
max(a, axis=1) np.
array([ 2, 9, 1, -1])
Prva, axis=0
, poišče največji element vzdolž indeksa
0
, torej vrne tabelo z največjim elementom v vsakem
stolpcu. Druga poišče največje element vsake vrstice, torej vzdolž
indeksa 1
.
Enako sum
:
sum(a, axis=0) np.
array([ 7, 0, -5])
sum(a, axis=1) np.
array([ -3, 15, 1, -11])
Kaj pa tole: obdržati želimo le vrstice, ki vsebujejo same nenegativne elemente.
a
array([[ 2, -7, 2],
[ 5, 9, 1],
[ 1, 0, 0],
[-1, -2, -8]])
>= 0 a
array([[ True, False, True],
[ True, True, True],
[ True, True, True],
[False, False, False]])
np.all(a >= 0)
nam pove, ali so vsi elementi
a
nenegativni. (Niso.)
all(a >= 0) np.
np.False_
Vendar nas to zanima v smeri stolpcev, v smeri 1.
all(a >= 0, axis=1) np.
array([False, True, True, False])
To uporabimo kot masko za vrstice a
-ja.
all(a >= 0, axis=1)] a[np.
array([[5, 9, 1],
[1, 0, 0]])
Bi znali prešteti, koliko pozitivnih elementov vsebuje posamični stolpec?
sum(a > 0, axis=0) np.
array([3, 1, 2])
Če slučajno pozabimo, koliko je π, je to preprosto izračunati.
Predstavljamo si krog s središčem v (0, 0) in polmerom 1. Ploščina tega kroga je πr2 = π12 = π.
Potem si predstavljamo kvadrat, ki ga očrtamo temu krogu. Postavimo ga vzporedno s koordinatami, z drugimi besedami, po x in y gre ta kvadrat od -1 do 1. Njegova stranica je 2 in ploščina, očitno 4.
Zdaj naključno izžrebamo N (recimo 1000) točk znotraj kvadrata. Kakšen delež teh točk je znotraj kroga? Ker je razmerje med ploščino kroga in kvadrata π/4, je pričakovati, da bo v krogu k = Nπ/4 točk.
Če smo torej pozabili π, sprogramiramo takšen poskus. Naključno izžrebamo koordinate N točk, pogledamo koliko jih je v krogu (to označimo s k) in potem iz gornje formule izrazimo π: π = 4k/N.
Gremo. Točke bomo izžrebali z
np.random.uniform(-1, 1, (N, 2))
, ki bo vrnil tabelo
velikosti (N, 2) z naključnimi števili med -1 in 1.
Da bo pregledneje bomo za začetek delali z desetimi točkami.
= 10
N
= np.random.uniform(-1, 1, (N, 2))
tocke
tocke
array([[ 0.00948207, -0.07791707],
[ 0.15539343, 0.21180141],
[ 0.71511941, 0.37776169],
[ 0.23962235, 0.52776044],
[ 0.45650711, -0.1539899 ],
[ 0.85403227, -0.76246257],
[-0.27235405, -0.64166477],
[-0.55670031, 0.26139958],
[ 0.79770905, -0.73099231],
[ 0.37104341, -0.93574917]])
Prvi stolpec so koordinate x, druge y. Razdaljo od točke do središča koordinatnega sistema (in s tem kroga) dobimo tako, da seštejemo kvadrate x in y ter to korenimo. Hvala, πtagora.
sum(tocke ** 2, axis=1) np.
array([0.00616098, 0.06900696, 0.65409967, 0.33594995, 0.23211163,
1.31072029, 0.4859104 , 0.37824498, 1.17068948, 1.01329972])
Vidimo? np.sum(..., axis=1)
sešteva "vodoravno", dobimo
torej ravno vsoto (kvadratov) prvega in drugega stolpca.
To korenimo.
sum(tocke ** 2, axis=1)) np.sqrt(np.
array([0.0784919 , 0.26269175, 0.80876428, 0.5796119 , 0.48177965,
1.14486693, 0.69707274, 0.61501624, 1.08198405, 1.00662789])
Točka je znotraj kroga s polmerom 1
, če je njena
razdalja od izhodišča manjša od 1.
sum(tocke ** 2, axis=1)) < 1 np.sqrt(np.
array([ True, True, True, True, True, False, True, True, False,
False])
(Na tem mestu opazimo, da je korenjenje nepotrebno. Koren je manjši od 1 natančno takrat, ko je število že samo manjše od 1.)
In zdaj samo preštejemo, koliko True
-jev imamo.
sum(np.sum(tocke ** 2, axis=1) < 1) np.
np.int64(7)
To je naš k
. Ponovimo na 1000 točkah in izračunajmo
π.
= 1000
N
= np.sum(np.sum(np.random.uniform(-1, 1, (N, 2)) ** 2, axis=1) < 1)
k
4 * k / N
np.float64(3.096)
No, recimo. Brez težav lahko ponovimo reč z več točkami.
= 1000000
N
= np.sum(np.sum(np.random.uniform(-1, 1, (N, 2)) ** 2, axis=1) < 1)
k
4 * k / N
np.float64(3.145504)
Spoznajmo še eno zanimivo funkcijo: np.unique
vrne
tabelo vseh različnih elementov neke tabele. Takole, recimo, dobimo
tabelo vseh, ki so sodelovale na dražbi.
np.unique(osebe)
array(['Ana', 'Berta', 'Cilka', 'Dani', 'Ema', 'Fanči', 'Greta', 'Helga'],
dtype='<U21')
Funkcija je posebej zanimiva zaradi tega, kar lahko vrne poleg te
tabele. Če ji dodamo argument return_counts=True
, vrne še
število pojavitev vsakega od teh imen - torej, kolikokrat je posamična
oseba dvigovala ceno (vštevši prvo ponudbo).
=True) np.unique(osebe, return_counts
(array(['Ana', 'Berta', 'Cilka', 'Dani', 'Ema', 'Fanči', 'Greta', 'Helga'],
dtype='<U21'),
array([ 6, 11, 13, 13, 11, 5, 15, 3]))
Ker vrne dve stvari, je to najbolj praktično razpakirati v dve spremenljivki. In že lahko povemo, kdo je bil najbolj zagret.
= np.unique(osebe, return_counts=True)
imena, visanja
imena[np.argmax(visanja)]
np.str_('Greta')
Drug zanimiv dodatni argument je return_inverse=True
.
Klic np.unique(a, return_inverse=True)
bo poleg tabele
unikatov vrnil tabelo, ki bo imela toliko elementov kot a
.
Vsak element pove, na katero mesto unikatne tabele se preslika posamični
element a
-ja.
= np.unique(osebe, return_inverse=True) imena, indeksi
osebe
array(['Berta', 'Ana', 'Berta', 'Fanči', 'Ana', 'Fanči', 'Ema', 'Greta',
'Ana', 'Greta', 'Ana', 'Fanči', 'Ana', 'Greta', 'Ana', 'Cilka',
'Greta', 'Fanči', 'Cilka', 'Dani', 'Berta', 'Dani', 'Berta',
'Dani', 'Berta', 'Dani', 'Berta', 'Dani', 'Berta', 'Dani', 'Berta',
'Cilka', 'Ema', 'Berta', 'Ema', 'Cilka', 'Berta', 'Cilka', 'Dani',
'Cilka', 'Greta', 'Dani', 'Cilka', 'Dani', 'Greta', 'Cilka',
'Greta', 'Ema', 'Dani', 'Greta', 'Cilka', 'Dani', 'Greta', 'Ema',
'Dani', 'Ema', 'Greta', 'Ema', 'Greta', 'Dani', 'Berta', 'Ema',
'Helga', 'Ema', 'Cilka', 'Helga', 'Greta', 'Ema', 'Cilka', 'Ema',
'Greta', 'Cilka', 'Greta', 'Cilka', 'Greta', 'Fanči', 'Helga'],
dtype='<U21')
indeksi
array([1, 0, 1, 5, 0, 5, 4, 6, 0, 6, 0, 5, 0, 6, 0, 2, 6, 5, 2, 3, 1, 3,
1, 3, 1, 3, 1, 3, 1, 3, 1, 2, 4, 1, 4, 2, 1, 2, 3, 2, 6, 3, 2, 3,
6, 2, 6, 4, 3, 6, 2, 3, 6, 4, 3, 4, 6, 4, 6, 3, 1, 4, 7, 4, 2, 7,
6, 4, 2, 4, 6, 2, 6, 2, 6, 5, 7])
imena
array(['Ana', 'Berta', 'Cilka', 'Dani', 'Ema', 'Fanči', 'Greta', 'Helga'],
dtype='<U21')
Z imena
in indeksi
lahko (če bi želeli, a
nam seveda ni treba) rekonstruiramo osebe. Prvi trije elementi
indeksi
so 1, 0, 1, 5. Pripadajoči elementi
imena
so (prvi) Berta, (ničti) Ana, prvi (Berta) in (peti)
Fanči - kar so natančno prvi elementi osebe
. Torej
imena[indeksi]
array(['Berta', 'Ana', 'Berta', 'Fanči', 'Ana', 'Fanči', 'Ema', 'Greta',
'Ana', 'Greta', 'Ana', 'Fanči', 'Ana', 'Greta', 'Ana', 'Cilka',
'Greta', 'Fanči', 'Cilka', 'Dani', 'Berta', 'Dani', 'Berta',
'Dani', 'Berta', 'Dani', 'Berta', 'Dani', 'Berta', 'Dani', 'Berta',
'Cilka', 'Ema', 'Berta', 'Ema', 'Cilka', 'Berta', 'Cilka', 'Dani',
'Cilka', 'Greta', 'Dani', 'Cilka', 'Dani', 'Greta', 'Cilka',
'Greta', 'Ema', 'Dani', 'Greta', 'Cilka', 'Dani', 'Greta', 'Ema',
'Dani', 'Ema', 'Greta', 'Ema', 'Greta', 'Dani', 'Berta', 'Ema',
'Helga', 'Ema', 'Cilka', 'Helga', 'Greta', 'Ema', 'Cilka', 'Ema',
'Greta', 'Cilka', 'Greta', 'Cilka', 'Greta', 'Fanči', 'Helga'],
dtype='<U21')
Tega seveda ne počnemo, saj osebe že imamo. Indekse uporabimo za kaj drugega.
Tole bo zahtevalo nekaj norega indeksiranja. Dovolj norega - in, predvsem, zelo potratnega - da bi to nalogo v praksi rešili s slovarji in zanko v Pythonu. Vendar bo poučno z vidika numpyja.
Pripravimo si tabelo ničel, ki ima toliko vrstic, kolikor je prodanih predmetov, in toliko stolpcev, kolikor je oseb. Torej: za vsako osebo drug stolpec.
= np.unique(osebe, return_inverse=True)
imena, indeksi
= np.zeros((len(koncni), len(imena)), dtype=int)
nakupi nakupi
array([[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]])
In zdaj nori del. Unique je vsaki osebi priredil indeks. Ana je 0,
Berta je 1 in tako naprej. indeksi
za vsako vrstico iz
podatkov pove, kakšen je indeks osebe, na katero se vrstica nanaša.
imena[indeksi]
so, vemo, pač kar imena oseb. Tabela
koncni
vsebuje indekse tistih vrstic, ki se nanašajo na
končne cene. indeksi[koncni]
so potem indeksi imen oseb, ki
so dejansko kupile posamični predmet.
indeksi[koncni]
array([5, 4, 2, 1, 3, 1, 6, 7])
Prvi predmet je kupila oseba 5, drugega oseba 4 in tako naprej. Oseba 1 je kupila dva predmeta. Kdo je to? Berta.
1] imena[
np.str_('Berta')
Sicer pa so končni kupci, po vrsti po predmetih,
imena[indeksi[koncni]]
array(['Fanči', 'Ema', 'Cilka', 'Berta', 'Dani', 'Berta', 'Greta',
'Helga'], dtype='<U21')
V cene[koncni]
najdemo, kot vemo že dolgo, končne cene
predmetov.
cene[koncni]
array([ 45, 29, 78, 83, 107, 15, 63, 21])
Zdaj pa izpolnimo tabelico nakupi. Vsaka vrstica se nanaša na nek
predmet. Ti so unikatni in jih oštevilčimo kar od 0
do
len(koncni)
(brez len(koncni)
). Vsak stolpec
se nanaša na neko osebo. Za vsako številko od 0 do len(koncni) in osebo
v indeksi[koncni]
(indeksi oseb, ki so kupile nek predmet)
v pripadajočo celico vpišemo ceno.
len(koncni)), indeksi[koncni]] = cene[koncni]
nakupi[np.arange(
nakupi
array([[ 0, 0, 0, 0, 0, 45, 0, 0],
[ 0, 0, 0, 0, 29, 0, 0, 0],
[ 0, 0, 78, 0, 0, 0, 0, 0],
[ 0, 83, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 107, 0, 0, 0, 0],
[ 0, 15, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 63, 0],
[ 0, 0, 0, 0, 0, 0, 0, 21]])
Prvi stolpec je Anin. Ta ni kupila ničesar. Drugi je Bertin: kupila je nekaj za 83 in nekaj za 15. In tako naprej. Koliko je zapravil kdo, izvemo, če seštejemo stolpce.
= np.sum(nakupi, axis=0)
poraba
poraba
array([ 0, 98, 78, 107, 29, 45, 63, 21])
Imena, ki pripadajo stolpcem, dobimo v imena
. In že
lahko izpišemo.
for ime, pora in zip(imena, poraba):
print(f"{ime:8}{pora:4}")
Ana 0
Berta 98
Cilka 78
Dani 107
Ema 29
Fanči 45
Greta 63
Helga 21
Še enkrat: uporabiti tako veliko tabelo za tako malo dela je nesmisel. Če bi vsaka oseba kupila več stvari in če bi lahko vsak izdelek prodali več osebam, pa bi posta(ja)lo smiselno. Vendar smo se tega dvojnega, vzporednega indeksiranja naučili, ker nam bo prišlo prav kasneje.
Po telovadbi iz prejšnjega razdelka je to trivialno. Začetne cene so
v cene[zacetni]
, koncne v cene[koncni]
.
Odštejemo ju, pa dobimo razlike, po katerih sprašuje naloga.
print(cene[koncni])
print(cene[zacetni])
- cene[zacetni] cene[koncni]
[ 45 29 78 83 107 15 63 21]
[31 29 44 50 30 15 27 16]
array([14, 0, 34, 33, 77, 0, 36, 5])
Imena predmetov najdemo v predmeti[zacetni]
ali
predmeti[koncni]
(oboje bo enako). Ker dobra vaga pomaga v
nebesa, bomo dodali še število ponudb in ponovili, kako oblikujemo
izpis.
= koncni - zacetni + 1
ponudb = cene[koncni] - cene[zacetni]
zvisanja
print(f"{'Predmet':25}{'ponudb':6}{'zvišanje':>12}")
print("-" * (25 + 6 + 12))
for predmet, pon, zvis in zip(predmeti[koncni], ponudb, zvisanja):
print(f"{predmet:25}{pon:6}{zvis:12}")
Predmet ponudb zvišanje
-------------------------------------------
slika 6 14
pozlačen dežnik 1 0
Meldrumove vaze 12 34
skodelice 12 33
kip 29 77
čajnik 1 0
srebrn jedilni servis 14 36
perzijska preproga 2 5
V neki naloga je Berta ugotovila, da ima najbrž sovražnika, ki vedno viša njeno ponudbo. Morali smo ji ga pomagati razkrinkati. V osnovi bi šlo tako:
-1] == "Berta" osebe[:
array([ True, False, True, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, True, False, True, False, True, False, True,
False, True, False, True, False, False, True, False, False,
True, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, True, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False])
-1] == "Berta") np.flatnonzero(osebe[:
array([ 0, 2, 20, 22, 24, 26, 28, 30, 33, 36, 60])
Zanimala nas bodo imena v vrsticah, ki so za temi vrsticami.
K indeksom torej prištejemo 1
(in to je razlog, da smo
odstranili zadnjo vrstico - če bi bila v zadnji vrstici slučajno Berta,
bi dobili prevelik indeks).
-1] == "Berta") + 1 np.flatnonzero(osebe[:
array([ 1, 3, 21, 23, 25, 27, 29, 31, 34, 37, 61])
-1]== "Berta") + 1] osebe[np.flatnonzero(osebe[:
array(['Ana', 'Fanči', 'Dani', 'Dani', 'Dani', 'Dani', 'Dani', 'Cilka',
'Ema', 'Cilka', 'Ema'], dtype='<U21')
In zdaj le še preštejemo, kolikokrat se na tem spisku pojavi posamično ime.
= np.unique(osebe[np.flatnonzero(osebe[:-1] == "Berta") + 1], return_counts=True) imena, za_berto
imena
array(['Ana', 'Cilka', 'Dani', 'Ema', 'Fanči'], dtype='<U21')
za_berto
array([1, 2, 5, 2, 1])
Največji je števec na indeksu
np.argmax(za_berto)
np.int64(2)
In pripadajoče ime - ha, pa jo imamo, sovražnico Berte - je
imena[np.argmax(za_berto)]
np.str_('Dani')
Vendar žal ni tako preprosto. Berta je kupila dva izdelka. Za njima je nekdo ponudil prvo ceno za naslednji izdelek, mi pa smo ga prišteli k Bertinim sovražnikom. Vrniti se bo treba malo nazaj.
Tole je maska, ki predstavlja vrstice, v katerih se je oglašala Berta (brez zadnje).
== "Berta")[:-1] (osebe
array([ True, False, True, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, True, False, True, False, True, False, True,
False, True, False, True, False, False, True, False, False,
True, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, True, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False])
Tole so vrstice, ki ne predstavljajo zadnje ponudbe za nek izdelek - spet brez zadnje.
1:] == predmeti[:-1] predmeti[
array([ True, True, True, True, True, False, False, True, True,
True, True, True, True, True, True, True, True, True,
False, True, True, True, True, True, True, True, True,
True, True, True, False, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True, True, True, False, False, True, True,
True, True, True, True, True, True, True, True, True,
True, True, False, True])
Da smo naredili pravilno, se prepričamo, če pogledamo zapisnik: ta se začne z
slika,Berta,31
slika,Ana,33
slika,Berta,35
slika,Fanči,37
slika,Ana,40
slika,Fanči,45
pozlačen dežnik,Ema,29
Meldrumove vaze,Greta,44
Meldrumove vaze,Ana,46
Šesti in sedmi element gornje tabele sta False
;
pripadajoči vrstici sta zadnji ponudbi, torej tisti, v katerih Bertine
vrstice ne smemo šteti kot ponudbo, ki jo je nekdo kasneje zviševal.
Zanimajo nas torej le vrstice, kjer se oglaša Berta in so ne-zadnja
ponudba, torej, v bistvu
(osebe == "Berta")[:-1] and predmeti[1:] == predmeti[:-1]
.
Žal and
nad tabelami ne deluje po elementih; razlogi so
tehnični in zgodovinski. Uporabiti moramo operator &
,
ki ga poznamo iz množic. Vlogo or
in not
pa
prevzameta |
in ~
. Na hitro jih preverimo na
preprostem primeru.
= np.array([True, True, False, False])
a = np.array([True, False, False, True])
b
& b a
array([ True, False, False, False])
| b a
array([ True, True, False, True])
~b
array([False, True, True, False])
Če bi kdaj prišlo prav, je tu še "ekskluzivni ali", ki vrne
True
, če je True
natanko eden od elementov, ne
pa oba.
^ b a
array([False, True, False, True])
S temi operatorji je povezana še ena komplikacija: premočni so.
Previsoko prioriteto imajo. Izraz a == b and c == d
bi
seveda pomeni (a == b) and (c == d)
, saj ==
veže močneje kot and
. Operator &
pa je
močnejši a == b & c == d
bi pomenilo
a == (b & c) == d
. (Zakaj jih niso naredili šibkejših?
Ker prihajajo iz drugega vica. Če bi nas zanimalo, ali je presek množic
a
in b
enak c
, bi napisali
a & b == c
in zdelo bi se nam logično, da to pomeni
(a & b) == c
. Sploh pa &
izvorno niti
ni operacija nad množicami, temveč nad števili. O tem nismo in ne bomo
govorili, vendar je to (in ne množice) razlog, da med tabelami v numpyju
uporabljamo &
.)
Izvedši torej vse o tem, kako izračunati konjunkcijo tabel, brž sestavimo pravo masko:
== "Berta")[:-1] & (predmeti[1:] == predmeti[:-1]) (osebe
array([ True, False, True, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, True, False, True, False, True, False, True,
False, True, False, False, False, False, True, False, False,
True, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False,
False, False, False, False])
In odtod vse steče po prejšnji poti.
= np.unique(
imena, za_berto == "Berta")[:-1] & (predmeti[1:] == predmeti[:-1])) + 1],
osebe[np.flatnonzero((osebe =True) return_counts
za_berto
array([1, 1, 5, 1, 1])
Da, da, prej je bilo [1, 2, 5, 2, 1]
. Ampak najhujša je
pa še vedno Dani.
V neki domači nalogi je bilo potrebno ugotoviti, za koliko se je cena posamičnega predmeta dvignila v zadnjih sedmih ponudbah; če je bilo ponudb manj, pa pač v zadnjih toliko, kolikor jih je bilo.
Če so indeksi vrstic z zadnjo ponudb v
koncni
array([ 5, 6, 18, 30, 59, 60, 74, 76])
potem moramo za vsakega za sedem vrstic nazaj,
- 7 koncni
array([-2, -1, 11, 23, 52, 53, 67, 69])
Seveda pa je to narobe, ker za nekatere predmete niti nimamo sedmih ponudb. Da je tako, nas posebej ostro opozorita prva izdelka, ki nas potisneta celo v negativne indekse. Indekse z vrsticami končnih ponudb moramo "omejiti" z indeksi vrstic s prvo ponudbo. Vzeti moramo tisto od njiju, ki je večja.
print(koncni - 7)
print(zacetni)
[-2 -1 11 23 52 53 67 69]
[ 0 6 7 19 31 60 61 75]
Kako to storiti? Preprosto; pravzaprav čisto očitno iz tega, kako smo ju izpisali v zgornji celici: zložimo ju v eno samo tabelo in izračunamo maksimum.
- 7, zacetni]) np.array([koncni
array([[-2, -1, 11, 23, 52, 53, 67, 69],
[ 0, 6, 7, 19, 31, 60, 61, 75]])
= np.max([koncni - 7, zacetni], axis=0) koncni_7
koncni_7
array([ 0, 6, 11, 23, 52, 60, 67, 75])
Za vsak slučaj preverimo, da se vrstice koncni
in
koncni_7
res nanašajo na iste predmete.
predmeti[koncni]
array(['slika', 'pozlačen dežnik', 'Meldrumove vaze', 'skodelice', 'kip',
'čajnik', 'srebrn jedilni servis', 'perzijska preproga'],
dtype='<U21')
predmeti[koncni_7]
array(['slika', 'pozlačen dežnik', 'Meldrumove vaze', 'skodelice', 'kip',
'čajnik', 'srebrn jedilni servis', 'perzijska preproga'],
dtype='<U21')
Ah, čemu bi to primerjali sami! Naj nam to naredi
numpy
.
== predmeti[koncni_7] predmeti[koncni]
array([ True, True, True, True, True, True, True, True])
Če smo še bolj leni, pa kar
all(predmeti[koncni] == predmeti[koncni_7]) np.
np.True_
Zdaj nam cene[koncni]
pove končne cene,
cene[koncni-7]
pa cene za sedem ponudb prej. Zanima pa nas
razlika.
- cene[koncni_7] cene[koncni]
array([14, 0, 21, 21, 15, 0, 16, 5])
In izpišimo.
for predmet, razlika in zip(predmeti[koncni], cene[koncni] - cene[koncni_7]):
print(f"{predmet:25}{razlika:3}")
slika 14
pozlačen dežnik 0
Meldrumove vaze 21
skodelice 21
kip 15
čajnik 0
srebrn jedilni servis 16
perzijska preproga 5
clip
Tole je za vtis. Ko sem programiral gornji primer, sem naredil, kot
sem naredil. Ko sem ga predeloval v zapiske, pa sem se spomnil na
funkcijo clip
. Ta prejme tabelo in meji; vse elemente, ki
so pod spodnjo mejo, nastavi na spodnjo mejo in te, ki so nad zgornjo,
potlači do zgornje.
Nek profesor izračuna oceno tako, da dosežene odstotke na pisnem izpitu deli z 10 in prišteje 1. Odstotki so lahko tudi večji kot 100, ker lahko študenti rešujejo dodatne naloge.
= np.array([63, 82, 45, 25, 125, 89])
odstotki
= odstotki // 10 + 1
ocene ocene
array([ 7, 9, 5, 3, 13, 9])
Pravila UL velevajo, da študentom ne dajemo ocen manjših od 5 in
večjih od 10. In tu uporabimo np.clip
.
5, 10) np.clip(ocene,
array([ 7, 9, 5, 5, 10, 9])
Za funkcijo sem vedel, ni pa mi prišlo na misel, da bi lahko bili meji tudi tabeli. Recimo, da so študenti poleg tega delali domače naloge in iz nekega razloga končna ocena ne more biti višja od ocen domačih nalog. In, tako kot prej, ne more biti nižja od 5.
= np.array([8, 6, 5, 5, 9, 10]) domace
print(ocene)
print(domace)
print(np.clip(ocene, 5, domace))
[ 7 9 5 3 13 9]
[ 8 6 5 5 9 10]
[7 6 5 5 9 9]
Potem bi lahko zgoraj namesto
max([koncni - 7, zacetni], axis=0) np.
array([ 0, 6, 11, 23, 52, 60, 67, 75])
napisali
- 7, zacetni, None) np.clip(koncni
array([ 0, 6, 11, 23, 52, 60, 67, 75])
Zgornje meje ni, zato None
.
Je to bistveno krajše, boljše? Če bi bili tabeli veliki in bi nam
bilo to mar, bi lahko s tem prihranili precej pomnilnika. Poleg tega je
iz imena funkcije np.clip
bolj jasno, kaj tule počnemo;
np.max
je pač ... nek maksimum.
Sicer pa tole pišem z drugim namenom: da pokažem, da ima numpy res ogromno funkcij, ki jih poznamo ali pa tudi ne in se nanje spomnimo ali pa tudi ne. Dalj kot ga uporabljamo, bolj bomo spretni in bolj elegantni bodo naši programi. Sicer pa pravzprav enako velja tudi za Python.