Python del 18: Histogram

Denne gangen skal vi se på hvordan vi kan bruke Python til å tegne den vanskeligste diagramtypen i Matematikk 2P: Histogram.

Vi husker at et histogram er et søylediagram hvor hver av søylene representerer et måleintervall og bredden på måleintervallet bestemmer bredden på søylene. Samtidig velger vi høyde på søylene slik at arealet til søylene blir proporsjonalt med antall målinger i intervallet.

Enkelt histogram

Vi tegner histogrammer med den samme funksjonen som vi bruker til å tegne søylediagrammer: bar. Når vi skal tegne histogrammer må vi ha fire argumenter:

  • En sortert liste med de nedre klassegrensensene
  • En liste med søylehøyder
  • En liste med søylebredder
  • Argumentet align=”edge” som sørger for at søylene starter på den nedre klassegrensen
 In [1]: plt.bar([0, 2, 3, 5, 7], [1, 2, 3, 4, 5], [2, 1, 2, 2, 1], align="edge") Out[1]:  

Vi kan også styre fargene til histogrammet.

  • color-argumentet angir fargen på søylene
  • edgecolor-argumentet angir fargen på kantene til søylene
 In [2]: plt.bar([0, 2, 3, 5, 7], [1, 2, 3, 4, 5], [2, 1, 2, 2, 1], align='edge', color='lightblue', edgecolor='blue') Out[2]:  

Vi har tidligere sett på hvilke farger vi kan velge.

Eksempel: Befolkning i norge

På SSB sine hjemmesider kan vi finne følgende tabell over den norske befolkningen sin aldersfordeling:

0 år53134
1-5 år290630
6-12 år448573
13-15 år192510
16-19 år252943
20-44 år1792220
45-66 år1511702
67-79 år613250
80-89 år190309
90 år eller eldre46098
Kilde: ssb.no/befolkning/statistikker/folkemengde

Vi ønsker å fremstille dette som et histogram.

 In [3]: %matplotlib In [4]: grenser = [0, 1, 6, 13, 16, 20, 45, 67, 80, 90, 110] In [5]: grupperteFrekvenser = [53134, 290630, 448573, 192510, 252943, 1792220, 1511702, 613250, 190309, 46098] In [6]: bredder = [x-y for x, y in zip(grenser[1:], grenser[:-1])] 

Den siste kodelinjen gjør mye

  • grenser[1:] ignorerer det første elementet i grenser-listen og gir oss derfor bare de øvre grensene.
  • grenser[:-1] ignorerer det siste elementet, og gir oss derfor de nedre grensene.
  • zip-funksjonen gjør at vi går gjennom et element fra de to underlistene samtidig. Disse blir tilordnet hendholdsvis x- og y- variablelen og den nye listen består av resultatet av utregningen x-y. Det vi gjør er altså det samme som vektorsubtraksjon.
 In [7]: høyder = [x/y for x, y in zip(grupperteFrekvenser,bredder)] 

Her regner vi elementvis ut frekvens/bredde.

Til slutt legger vi til forklarende tekst:

 In [8]: plt.bar(grenser[:-1],høyder,bredder,align="edge", color='lightblue', edgecolor='blue') In [9]: plt.title("Norges befolkning") In [10]: plt.xlabel("alder") In [11]: plt.ylabel("personer") 

Funksjon for å tegne histogram

Vi ser fra det forrige eksemplet at vi trenger to biter med informasjon for å tegne et histogram: grenser og frekvenser. Ut i fra dette kan vi regne ut søylehøydene og søylebreddene. Derfor passer det å lage en funksjon som tegner et histogram ut i fra de to opprinnelige parameterne.

 def tegnHistogram(grenser, grupperteFrekvenser,tittel="Histogram", xlabel ="", ylabel=""):     bredder = [x-y for x,y in zip(grenser[1:], grenser[:-1])]     høyder = [x/y for x, y in zip(grupperteFrekvenser,bredder)]     plt.bar(grenser[:-1], høyder, bredder, align="edge", color='lightblue', edgecolor='blue')     plt.title(tittel)     plt.show() 

De siste tre parametrene styrer den forklarende teksten. Hvis vi ville kunne vi også lagt til parametre for farger.

Vi tester funksjonen med de samme dataene som i det forrige eksemplet:

 In [13]: tegnHistogram(grenser, grupperteFrekvenser, "Norges befolkning","alder","personer") 

Endre histogramstil

Vi kan også endre stilen på histogrammer

 In [14]: with plt.xkcd():     ...:    tegnHistogram(grenser, grupperteFrekvenser, "Norges befolkning","alder","personer") 

Ofte får vi dataene som enkeltverdier og må gruppere dataene selv. Dette kan vi lett gjøre ved hjelp av for-løkker.

For å finne ut om en verdi er inni et intervall bruker vi listefiltrering: [ element for element in liste if logisk test]

Vi går gjennom elementene i listen og tar bare med de elementene som oppfyller den logiske testen.

For å trekke ut alle elementer mellom to verdier må vi da skrive: [ verdi for verdi in liste if a <= verdi < b ]

Hvor a og b er to tall. I den logiske testen brukte vi to nye operatorer: <= (mindre enn eller lik) og < (mindre). Denne testen returnerer sann dersom verdi er større enn eller lik a, og samtidig mindre enn b.

Dette uttrykket trekker ut alle verdier fra en liste mellom 0 og 2:

 In [15]: observasjoner = [0, 0, 2, 6, 7, 2, 1, 4, 1, 7, 2, 9, 4, 10, 9, 3, 7, 9, 2, 1] In [16]: [verdi for verdi in observasjoner if 0 <= verdi <2] Out[16]: [0, 0, 1, 1, 1] 

hvis vi er interessert i antall observasjoner i listen bruker vi funksjonen len på den filtrerte listen:

 In [17]: len([0,0,1,1,1,]) Out[17]: 5 

eller direkte:

 In [18]: len([verdi for verdi in observasjoner if 0 <= verdi <2]) Out[18]: 5 

For å gruppere alle tallene legger vi dette uttrykket i en for-løkke hvor vi går gjennom alle nerdre og øvre grenser:

 In [19]: nedreGrenser = [0,2,5,8] In [20]: øvreGrenser = [2,5,8,10] In [21]: grupperteFrekvenser = [] In [22]: for nedreGrense, øvreGrense in zip(nedreGrenser, øvreGrenser):     ...:    grupperteFrekvenser.append(len([verdi for verdi in observasjoner if nedreGrense <= verdi < øvreGrense])) In [23]: grupperteFrekvenser Out[23]: [5, 7, 4, 3] 

Vi ser at vi har to lister, øvre og nedre grenser, vi kan koble disse sammen til en liste, grenser siden vi kan hente ut listene over øvre og nedre grenser som underlister.

Til slutt kan vi samle filtreringskoden som en funksjon:

 def grupperFrekvenser(grenser, observasjoner):     gruppert = []     for nedreGrense, øvreGrense in zip(grenser[:-1],grenser[1:]):         gruppert.append(len([x for x in observasjoner if nedreGrense <= x < øvreGrense]))     return gruppert 

Tester denne funksjonen:

 In [25]: observasjoner = [0, 0, 2, 6, 7, 2, 1, 4, 1, 7, 2, 9, 4, 10, 9, 3, 7, 9, 2, 1] In [26]: grenser = [0, 2, 5, 8, 10] In [27]: grupperteFrekvenser = grupperFrekvenser(grenser, observasjoner) In [28]: grupperteFrekvenser Out[28]: [5, 7, 4, 3] 

Når vi har gruppert observasjonene kan vi tegne dem som et histogram:

 In [29]: %matplotlib inline In [30]: tegnHistogram(grenser, grupperteFrekvenser) 

Vi har til nå sett på grupperte frekvenstabeller. Disse har to attributter: grenser og frekvenser.

I tillegg har vi gjort en del beregninger: vi har regnet ut bredder, høyder, øvre- og nedre grenser. Disse beregningene blir bare riktige hvis vi gjør dem på grensene og frekvensene til en gruppert frekvenstabell. Derfor passer det bra å samle attributtene og metodene i en klasse som vi kaller frekvenstabell:

 import matplotlib.pyplot as plt  class gruppertFrekvenstabell:     def __init__(self,grenser, frekvenser,beskrivelse=""):         # konstruktør         self.grenser = grenser         self.frekvenser = frekvenser         self.beskrivelse = beskrivelse              def hentNedreGrenser(self):         # funksjon som returnerer de nedre grensene         return self.grenser[:-1]          def hentØvreGrenser(self):         # funksjon som returnerer de øvre grensene         return self.grenser [1:]          def hentFrekvenser(self):         # funksjon som returnerer klassefrekvenser         return self.frekvenser          def hentBredder(self):         # funksjon som returnerer klassebreddene         return [x-y for x,y in zip(self.grenser[1:], self.grenser[:-1])]          def hentHøyder(self):         # funksjon som returnerer klassehøydene         return [x/y for x, y in zip(self.frekvenser,self.hentBredder())]              def histogram(self):         # funksjon som representerer den grupperteFrekvenstabellen som et histogram         plt.bar(self.hentNedreGrenser(),self.hentHøyder(),self.hentBredder(),align="edge", color='lightblue', edgecolor='blue')         plt.title(self.beskrivelse)         plt.show()  

Vi ser at de fleste metodene bare returnerer enkle beregninger og attributtene. Dette er fordi vi ikke ønsker å bruke objektatributter direkte utenfor objektene selv. Fordelen med å gjøre det slik er at vi kan endre på den indre strukturen til klassen uten å endre resten av programmet vårt. For eksempel kan vi endre på klassen vår slik at vi lagrer grenser og frekvenser som en ordliste uten at vi trenger å endre på måten vi henter ut verdiene.

Vi legger klassedefinisjonen inn et Python-program og kjører dette. Etterpå kan vi teste den nye klassen i konsollvinduet:

 In [32]: gf1 = gruppertFrekvenstabell(grenser,grupperteFrekvenser,"Tilfeldig generert histogram") In [33]: gf1.histogram() 

Vi ønsker å samle alt som har med grupperte frekvenstabeller i denne klassen. I den forbindelse vil vi at grupperFrekvenser-funksjonen skal kobles til gruppertFrekvenstabell-klassen.

grupperFrekvenser tar inn grenser og data og returnerer grenser og grupperteFrekvenser, det vil si de to listene vi har koblet sammen til et gruppertFrekvenstabell–objekt.

Vi kan ikke gjøre denne funksjonen til en vanlig metode, siden vi har ikke noe gruppertFrekvens-objekt å kalle før vi har kalt grupperFrekvenser. Vi kan i stedet gjøre denne funksjonen til en statisk metode. En statisk metode kaller vi med: Klassenavn.statiskMetode()

Vi gjør funksjonen til en statisk metode kalt lagFraObservasjoner som returnerer et gruppertFrekvenstabell-objekt.

Legger denne koden til på slutten av klassedefinisjonen vår:

     @staticmethod     def lagFraObservasjoner(grenser, observasjoner, beskrivelse = ""):         gruppert = []         for nedreGrense, øvreGrense in zip(grenser[:-1],grenser[1:]):             gruppert.append(len([x for x in observasjoner if nedreGrense <= x < øvreGrense]))         return gruppertFrekvenstabell(grenser, gruppert, beskrivelse)  

@staticmetod sier at den neste metoden skal være statisk.

Vi kjører programmet vårt med klassedefinisjonen på nytt og tester om den nye statiske metoden virker:

 In [35]: gf2 = gruppertFrekvenstabell.lagFraObservasjoner(grenser,observasjoner,"Gruppert frekvenstabell fra observasjoner") In [36]: gf2.histogram() 

En metode som lager et objekt kaller vi for en factory-metode. Siden det bare er mulig å ha en konstruktør, men det ofte er mange måter å lage et objekt på, må vi av og til bruke slike metoder.

Yorumlar

Bu blogdaki popüler yayınlar

Tinder mature jenson

Drool woods cullen

3some transexual panther