Lektion 9 - Datahantering

Innan du börjar

I denna lektion skall du använda paketet Matplotlib. Du behöver därför installera detta paket till Thonny. Instruktion hur du gör detta: Se första delen av följande video.

För dig som inte använder Thonny: Här finns instruktioner för det.

Om du får problem att installera paketet eller att få det att fungera, se till att be om hjälp under labbarna.

Läs instruktionerna noga.

  • Förkunskapskrav: funktioner, listor, lexikon
  • Begrepp som introduceras: paketen cvs, matplotlib(, valfritt: pytest). Vi ger exempel på hur man använder dessa paket länkade från denna fil.
  • Förväntad arbetstid: Schemalagd handledningstid inkl redovisning: 8 timmar. Utöver det räkna med eget arbete med lika många timmar.
  • Arbetssätt: Arbeta gärna tillsammans med någon, men skriv egen kod. Diskutera med varandra! Du måste själva kunna förklara och diskutera din kod när du redovisar.
  • Redovisning: Du kommer att få redovisa dina lösningar på A, B och C för lärare/handledare enligt tiderna på kurssidan.
  • De data som används i lektionen har hämtats från Världsbanken, Gapminder och SMHI. Det finns mängder av öppna dataset hos båda dessa källor. Titta gärna vidare själv!

I. Importera data

Data kan finnas i mängder av olika format, även när skaparen aktivt har tänkt att de ska användas för vidare analys (.xls, .json, .txt, ...). Komma-separarerade värden (comma-separated values), .csv, är ett enkelt, portabelt och populärt format för tabelldata. I Python finns modulen csv som kan användas med .csv-datafiler.

Läsa in CSV-data

Om vi vill läsa in CSV-filer i ett Pythonskript kan man använda modulen csv tillsammans med det vi redan har sett om att hantera filer och text. Tänk till exempel att vi har filen people.csv med följande innehåll:

No, Name, country
1, Alex, USA
2, Erik, Sweden
3, Cheng, China

Då kan vi läsa in den med följande kod. Vi använder csv.reader som ger oss ett itererbart objekt över raderna i filen. Det finns fler exempel i den officiella Pythondokumentationen för csv-modulen.

In [1]:
import csv

with open('people.csv', 'r') as csvFile:
    reader = csv.reader(csvFile)
    for row in reader:
        print(row)
['No', ' Name', ' country']
['1', ' Alex', ' USA']
['2', ' Erik', ' Sweden']
['3', ' Cheng', ' China']

Förbearbetning

Det enklaste är att importera csv-data rakt av som en tabell (en lista där varje rad är en lista med kolumnvärdena). Det blir däremot ofta ganska opraktiskt. Om vi vill hitta raden för ett visst land måtse vi då gå igenom hela listan. Ofta vill man i stället kunna söka lätt och då blir lexikon lämpliga.

Obs! Det är vanligt att den första raden i en CSV-fil är rubriker i stället för datainnehåll (så var det i people.csv ovan). Om man vet vilka kolumner som finns vill man oftast bara ignorera rubrikraden.

Obligatorisk uppgift A:

  1. Ladda ned filen CO2Emissions_filtered.csv från kurssidan. Du kan öppna den i en texteditor, för PC exvis Thonny, Notepad eller Notepad++, för Mac exvis TextEditor för att förstå strukturen på filen. Använd ej kalkylbladsprogram (som Excel, Numbers, Sheets) för detta, de kan ändra i filen. När du förstår hur filen är uppbyggd skriver du en funktion load_csv(filename) som har filename som parameter och returnerar ett lexikon med landskoder (skrivna med enbart gemener) som nyckel och listan med koldioxidutsläpp per år som värde.

Obs! Alla rader i filen är nödvändigtvis inte är uppbyggda exakt på samma sätt, se t ex rad 15. Du ska inte ändra i filen CO2Emissions_filtered.csv utan ditt program ska kunna hantera detta.

Tips! Testa din kod genom att skriva ut några element i början eller slutet av ditt lexikon.
Precis som vi kan använda listbeskrivningar (list comprehensions) kan vi använda lexikonbeskrivningar (dict comprehensions) för att bygga lexikon på ett effektivt sätt!

Exempel:

l = [
    ['a', '10', '9'],
    ['C', '-8', '0'],
    ['P', '4', '2']
]

d  = {v[0]: v[1:] for v in l}

print(d)
> {'a': ['10', '9'], 'C': ['-8', '0'], 'P': ['4', '2']}

Anmärkning 1: När du använder csv-paketet för att läsa en CSV-fil får värdena typen str. Eftersom det är mätvärden vi har att göra med är det mycket rimligare att de lagras som typen float för de beräkningar vi ska göra sedan. Som vi redan har sett tidigare i kursen går det lätt att konvetera en lista med str-element till en lista med float-element genom listbeskrivningar eller funktionen map(). Ett kort exempel:

strList = ['321','5433', '11']
# Med en listbeskivning
floatListComp = [float(s) for s in strList]
# Med funktionen map()
floatListMap = list(map(float, strList))

Anmärkning 2: I filen CO2Emissions_filtered.csv är landskoderna skrivna med versaler. Eftersom vi vill kombinera informationen från den här filen med andra data i uppgiften måste de omvandlas till gemener (små bokstäver). Du kan göra det med funktionen lower(), som förstås kan användas direkt i en dict-beskrivning. Mer information om lower() finns i den officiella dokumentationen. Du kan förstås söka vidare på nätet också för att se fler exempel.

Valfritt: Hela omvandlingen av strängar till flyttal och landskoderna till gemener kan göras på en enda rad direkt när data läses in. På den raden kan det hända att du vill använda både list- och lexikonbeskrivningar.

II. Presentera data

Matplotlib är den mest populära Pythonmodulen för grundläggande visualisering och plottning. Det finns många olika alternativ. Delar av biblioteket är också gjorda för att likna motsvarande funktioner i Matlab, men de är inte identiska.

Den finns officiella exempel på hur man kan använda matplotlib här och här.

Tvådimensionella data

Vi kan börja med ett enkelt exempel. Du kan provköra det själv för att se att matplotlib fungerar.

import matplotlib.pyplot as plt
from math import pi,sin

# Data for plotting
L = 100
time = list(range(L))
Voltage = [sin(2*pi*t/L) for t in time]

fig, ax = plt.subplots()
ax.plot(time, Voltage)

ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Example 1')
ax.grid()

fig.savefig("test.png")
plt.show()

Förklaring rad för rad

  • import matplotlib.pyplot as plt importerar en delmodul i matplotlib och ger den aliaset plt.
  • fig, ax = plt.subplots skapar figurens ram.
  • ax.plot(time, Voltage) ritar upp de data som tidigare beräknades i time och Voltage. I detta fall är innehållet listor, men man kan också använda arrayer, som är vanliga i paketet numpy.
  • ax.set(xlabel='time (s)', ylabel='voltage (mV)', title='Example 1') sätter ut etiketter för de båda axlarna och en rubrik för hela figuren.
  • ax.grid() skapar ett rutnät i figuren, vilket kang göra det lättare att läsa av numeriska värden visuellt.
  • fig.savefig("test.png") sparar figuren som en .png-fil med angivet filnamn. Matplotlib stöder flera olika bildformat. Biblioteket stöder både vektorgrafik (som .pdf, .eps, .svg) och bitmappsformat (som .png och .jpg). Om man kan välja brukar vektorgrafik ge bättre resultat när figurerna ska visas, särskilt vid utskrifter.
  • plt.show() visar figuren i ett nytt fönster.

Tips! Det vanligaste sättet att lära sig matplotlib är att titta på exempel som gör liknande saker. Det är viktigt att du försöker titta på några exempel innan du skriver koden för ditt eget projekt.

Obligatorisk uppgift B:

  1. Använd lexikonet från uppgift A för att ta fram data för de fem nordiska länderna (Danmark, Finland, Island, Norge och Sverige) för åren 1960 till och med 2014. Landskoderna för dessa länder är 'dnk', 'fin', 'isl', 'nor', 'swe'. Tidsintervallet kan skapas som en lista med kommandot time = list(range(1960, 2015)). Om du tittar på data direkt ser du att det finns en del störande variationer, eller brus.
  2. Det går att minska bruset genom att använda de två olika medelvärdesfunktionerna från lektion 6, smooth_a och smooth_b. Använd smooth_a och smooth_b för att jämna ut data över en total period om 11 år, det vill säga 5 datapunkter ytterligare runt ett mittår. Illustrera resultaten från smooth_a med heldragen kurva, smooth_b med streckad kurva och ursprungsdata med prickad kurva, för alla fem länderna. Du ska alltså totalt plotta 15 dataset. Se till att inte duplicera kod i onödan. Använd en färg per land för att skilja dem åt, men låt alla kurvor för samma land använda en och samma färg. Nedan visas ett exempel på hur resultaten kan se ut, men välj själv vilket exakt utseende du tycker blir bäst så länge du följer ovanstående krav.
In [6]:
%run plot2D.py

Obligatorisk uppgift C:

Skriv ett Python-program som ritar upp fyra lådagram (boxplot)som visar hur medeltemperaturen på några platser i Sverige har varierat under maj månad från den första mätningen 1722 till den sista mätningen 2019.


Du ska hämta data från filen Uppsalas temperaturserie 1722-2019 Filen med data är komprimerad (zip) och innehåller både dataserien (dygnsvärden för perioden 1722-2019) som en textfil (.dat) samt en filbeskrivning (.txt). När du laddat ned zip-filen och unzippar den kommer du få de båda filerna. Du ska använda textfilen uppsala_tm_1722-2019.dat. Observera att det inte är en csv-fil!


Glöm inte att erkänna upphovsmakarna, se filen uppsala_tm_1722-2019.


Du ska för varje år per sekel beräkna medeltemperaturen för maj månad och spara dessa i en lista. Använd temperaturerna i den fjärde kolumnen. Det kommer att bli 4 listor, en för varje sekel, där elementen är medeltemperaturerna för varje år. Listorna ska vara data för Pythons boxplot-funktion. Resultatet bör se ut så här:



Sätt ut en titel och namn på axlarna.
Frivilligt: använt funktionen annotate för att skriva in årtalen ovanför varje lådagram.


Tips 1: Använd funktionen split() eller ett reguljärt uttryck (lektion 7) för att ta ut enbart decimaltalen och hoppa över mellanslagen från varje rad i filen.


Tips 2: Skapa två listor, en som ska innehåller medeltemperaturerna för varje år under ett visst århundrade, och en som innehåller fyra listorna med medeltemperaturerna. Ge den senare listan till boxplot-funktionen.


Tips 3: Du kan ha nytta av matplotfunktionerna:
subplots, annotate (frivilligt), set_xticklabels, set_title, xlabel, ylabel, boxplot, show

Frivilliga övningar:

Visualisera data med mer än två dimensioner

Om man har data med mer än två dimensioner kan de ibland ändå visualieras effektivt genom att använda punkter med två rumsliga dimensioner och sedan låta attribut för punkterna som färg och radier förmedla ytterligare information. Nedan finns ett kort exempel:

In [2]:
import matplotlib.pyplot as plt
import random

random.seed(20191126)
fig, ax = plt.subplots(figsize=(6, 6))
N = 50
for color in ['tab:blue', 'tab:orange', 'tab:green']:
    x = [200**random.random() for i in range(N)]
    y = [200**random.random() for i in range(N)]
    scale = [500*random.random() for i in range(N)]
    ax.scatter(x, y, s=scale, c=color, label=color, alpha=0.3, edgecolors='none')

ax.legend()
plt.show()

Om man ser till datasetet i sin helhet finns fyra dimensioner: x-koordinat, y-koordinat, color och scale. Koordinaterna har slumpats mellan 0 och 200 enligt en exponentialfördelning. Storleken på varje punkt har slumpats linjärt mellan 0 och 500.

ax.scatter(x, y, c=color, s=scale, label=color, alpha=0.3, edgecolors='none') är kärnpunkten i exemplet. Kommandot tar x- och y-koordinater i de två första argumenten. Dessutom används namngivna argument för att meddela color (parametern c) och scale (parametern s). Argumentet alpha, som är ett värde mellan 0 och 1, styr genomskinlighet. De första värdena i listoran x och y ritas först och täcks sedan successivt av senare värden.

Uppgift:

  1. Hämta filen population.csv. Använd funktionen load_csv() som du skrev tidigare för att importera dina data. Som en enkel kontroll för att se att allt fungerar plottar du befolkningen i Bolivia, Venezuela, Chile, Ecuador och Paraguay. Se till att det finns x- och y-etiketter i figuren och att figuren har både rubrik och teckenförklaring.
  2. Det är vanligt att man vill kombinera data från flera olika källor. Det kan innebära att data finns i olika format, eller att det inte finns ett totalt överlapp mellan olika uppgifter så att vissa element saknas. I så fall kan man behöva formatera om sina data. I det här fallet förhåller det sig så att dataseten från CO2Emissions_filtered.csv och population.csv inte innehåller exakt samma länder (vissa länder saknas från det första datasetet för att data saknas för hela perioden 1960-2014). Skriv en funktion intersection(list_1, list_2). Funktionen ska ta in två listor med landskoder och returnera en ny lista som bara innehåller de koder som finns i både list_1 och list_2. Du kan välja att använda pytest för att skriva några test som visar att funktionen fungerar korrekt.

    Exempel:

    intersection(['fra', 'deu', 'ita', 'nld', 'lux'], ['bel', 'ita', 'fra', 'nld'])
    
    > ['fra', 'ita', 'nld']
  3. Skapa en scatterplot med CO2-utsläpp avsatta mot befolkning för år 2014. Ta med alla länder som finns i båda dataseten. (Det bör bli 141 länder.)
  4. Använd en log-log-skala i figuren för att den ska bli tydligare. Se till att ha med x- och y-etiketter och diagramrubrik. Lägg till textförklaring med landskod för varje punkt. Ett exempel på hur figuren kan se ut visas nedan.
In [3]:
%run plotScatter.py
  1. Även om figuren ovan visar en trend kan man vilja förmedla mer information. Det kan till exempel säga en del att färgmarkera länderna efter kontinent. Det finns kontinentkoder i datafilen country_continent.csv. Nedan visas ett exempel på hur figuren kan se ut då.
In [4]:
%run plotScatterContinent.py
  1. Det finns massor med möjligheter! Det går till exempel att ge varje punkt en färg efter förändringen (positiva värden för ökning) i CO2-utsläpp mellan 1994 och 2014. Vi föreslår att du använder färgskuse (the color map coolwarm) (se https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html). "Eng: Since some countries (China, the US, and India for instance) have increased their emission tremendously more than others, it is difficult to see differences between all the other countries. scatter can take two optional parameters to set the range of the colormap, that is vmin and vmax. Adjust the colormap to fix this issue. An example of the output is shown below."
In [5]:
%run plotScatterDiff.py