Lektion 8 - Datahantering, innehåller OU4

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 csv, matplotlib. Vi ger exempel på hur man använder dessa paket länkade från denna fil.
  • Förväntad arbetstid: Schemalagd handledningstid inkl redovisning: 10 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.
  • OU4 består av tre deluppgifter: A, B och C.
  • Redovisning: Du kommer att få redovisa dina lösningar på uppgift 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!
  • Notera att den pythonkod du redovisar skall följa kodningsreglerna
    Speciellt:
    • Strukturen på en python-fil: import-satser överst, sedan funktions-definitioner, sist funktionsanrop.
    • Ingen "lös" kod mellan funktions-definitionerna, all lös kod samlas till efter funktions-definitionerna.
    • Rimliga variabelnamn.
    • Inga globala variabler.
    • Utnyttja redan givna funktioner eller funktioner du redan har skrivit
  • Backup:När du arbetar på egen dator måste du se till att det löpande görs backup på de filer (py-filer) som du skapar under kursens gång. Om du har filerna enbart lagrade på din dator finns risk att allt försvinner vid ett datorhaveri eller vid stöld.
  • Samarbete, fusk och plagiat:
    • Muntligt samarbete är tillåtet och rekommenderas.
    • Men den kod du redovisar skall du skriva helt själv, förutom given kod som finns på kursens hemsida.
    • Du får INTE på något sätt dela med mig av kod/svar till någon annan, förutom det muntliga samarbetet.
    • Du får INTE ta del av kod/svar någon annan på något sätt delar med sig av, förutom det muntliga samarbetet.
    • Vid misstanke om fusk eller plagiat, precis som vid annan examination, kan detta anmälas till universitetets disciplinnämnd.

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åste 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, eftersom filen innehåller väldigt mycket data.
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:

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

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

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 (ett annat programmeringsspråk), 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 medelvärdesfunktionen (funktionerna) från lektion 6, smooth_a, och den frivilliga uppgiften smooth_b om du gjort den. Använd smooth_a, och smooth_b om du gjort den, 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, om du gjort den, med streckad kurva, och ursprungsdata med prickad kurva, för alla fem länderna. Du ska alltså totalt plotta 10 dataset, 15 om du gjort smooth_b. 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, om du testar både med smooth_a och smooth_b, 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:

Sedan 1722 har det samlats väderdata i Uppsala. Mätserien fyller 300 år 2022. Här är en länk till ett kort reportage för den intresserade.


I den här uppgiften ska du använda delar av väderdatat och illustrera i lådagram. 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 webbsidan Uppsalas temperaturserie. På denna webbsida finns en fil att ladda ned (uppsala_tm_1722-2020.zip) . 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

Resten av denna lektion innehåller 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.

Övningar:

  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. Skriv 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