Python Codes Zusammenfassung

Auf dieser Seite, habe ich eine Reihe an verschiedenen Codes aufgelistet, um zukünftigen Data Scientisten ein breites Spektrum an Beispielen anzubieten, zur Bewältigung üblicher Aufgaben im Bereich des Umgangs mit Daten.

Ich hoffe, dass meine Leserschaft findet, wonach Sie sucht 🔥

Inhaltsverzeichnis

Python-Basics für Data-Scientisten


F-Strings

F-strings können in Python verwendet werden, um Ergebnisse, die in Variablen enthalten sind, dynamisch anzuzeigen.

  • Beispiel zum Einsatz eines F-Strings:
import math 

def day_converter(hour_lag):
    """
    What does this function do?: 
    
    This Function will convert the index of the cross-correlation to the 'real lag' and also 
    converst the hours blagged behind into a more human readable lagged value, which will 
    be in 'days' and 'hours' instead of only 'hours'.
    
    Why is it important?:

    Suppose we know that the **index's 167th lag** has the highest correlation. From this fact, 
    we can conclude that the "true" 168th lag - which corresponds to the 167th index of the lag 
    in the list - is the most important lag that we are interested in. However, the problem that 
    we have is: what day & hour corresponds to the 168th lag (here: 168 hours, because our data 
    is the HOURLY price, e.g. the data has the unit 'hours') in the past? This is where this 
    converter comes into play.
    
    Example:
    
    --> Input: day_converter(168)
    > 'Converted, the 168-th lag is 7 days and 0 hours in the past.'
    
    """
    true_lag = hour_lag + 1
    days = math.floor(true_lag/24)
    diff = true_lag - (days *24)
    print("Converted, the {0}-th lag is {1} days and {2} hours in the past.".format(true_lag, days, diff))

day_converter(169) # call the function, to check if it worked? --> you can play around and change the input 
                   # --> it is always correct and shows the power of f-strings!

List Comprehensions

List Comprehensions sind ein Konzept, die zu den beliebtesten und einzigartigsten Funktionen in Python gehören. Es ist im Grunde ein “For Loop”, bei der eine Funktion auf jedes der Elemente im Loop angewendet wird. Der Output einer “List Comprehension” eine List.

Beispiele:

List comprehension, bei der folgende function angewendet wird: x2

squares = [n**2 for n in range(10)] # apply f(x) = x^2 on a list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
squares
## [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(type(squares)) # Check output: should be a 'list'
## <class 'list'>

List Comprehensions sind besonders nützlich, wenn man über jedes Element einer Spalte iterieren möchte und dabei eine Funktion auf diese anwenden möchten.

List comprehension mit einem if-Statement:

planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune'] # this is the list we will loop through

short_planets = [planet for planet in planets if len(planet) < 6]
short_planets
## ['Venus', 'Earth', 'Mars']

Dictionaries

Hinzufügen neuer Key-Value-Paare zu einem Dictionary

dictionary_name[key] = value

Vergleich zweier Datensätze bezüglich deren Zeilen & Spalten

Der Kontext:
Hier vergleichen wir zwei DataFrames anhand ihres Ähnlichkeitsgrades. Die Schlüsselfrage, die ich mit dem nachstehenden Code zu beantworten versucht habe, lautet: War ich in der Lage, denselben DataFrame nach einer Datenbereinigung zu replizieren, relativ zu einem Referenz-Datensatz?

### Step 1): create an empty(!!) dictionary where we will store the different results of the error-difference 
###          between the two ddsets.
result = dict()

### Step 2) let's select one error-metric of our choice. Here: "Mean-Squared-Error"
from sktime.performance_metrics.forecasting import MeanSquaredError
end = "2021-03-12 22:00:00+00:00" # let's create the dataframe of the same length as PW's benchmark-dataframe
for col in df_joffs_replikation: # we iterate over each column(!!)
    result[col] = MeanSquaredError(df_JM[col][:end]- df_PW[col][:end], square_root=True) # For EACH column passiert 
    # folgendes: the dictionary-KEYS will be saved as the columns' name (in the empty dictionary), while the 
    # dictionary-VALUES will be saved as the Outputs of the MeanSquaredError()-Function.
    # > Important to note: We slice our ddset until PW's benchmark-ddset (via the `end`-variable, which denotes 
    #   the 12.03.2021, e.g. the end of PW's notebook), when performing the "vector"-substraction!!

### Step 3) Check what the results are
result # check, if it worked?
print(result.keys()) # further check: did it REALLY work for all columns? --> yes, since it printed out all the 21 keys!

### Step 4) Berechne den durchschnittlichen Fehler (= meine DF-Values - PW's DF-Values)
"""
> Ziel: Durchschnittlich begangener Fehler (= MAPE) pro Variable (= Spalte):
    --> Wir wollen überprüfen, wie gross die Wert-Abweichungen von Philipp's Notebook VS. Joffrey's Notebook 
    sind (jeweils für <u>jeden</u> Wert, in allen 18 Spalten). Dh, **uns interessiert die _Verteilung_ der 
    Abweichungen** (also, wie gross im Durchschnitt sind die Fehler und wie hoch ist deren Streuung).

Für den durchschnittlichen Fehler / Abweichung meiner replizierten Tabelle VS. diejenige von Philipps habe ich 
den *Mean Absolute Percentage Error (MAPE)* gewählt (siehe 'Step 2' oben).
"""
for value in result.values(): # iteriere über den oben erstellten Dictionary und berechne die durchschnittliche 
                                   # absolute Abweichung für jede Spalte
    print(value.mean()) # sollte 18 Resultate printen, da wir 18 Spalten überprüfen --> Wir erwarten hier überall 
    # etwa die Ziffer "0", was also jeweils einem durchschnittlichen (absoluten) Fehler von '0' entspricht, PRO Spalte!
    
### Step 5): Berechnung der Standardabweichung der (absoluten) Abweichungen für jede Spalte
for value in result.values():
    print(value.std())
  • Hinweis: Der obige Code kann nur auf DataFrames ausgeführt werden, die die folgenden Bedingungen erfüllen:

    • Alle Spalten von beiden DataFrames sollten die gleichen Namen besitzen!
    • Beide DFs sollten die gleiche Anzahl von Zeilen (= Beobachtungen) haben. Dies habe ich durch die Verwendung einer Variable namens end erreicht, die über pdf_JM[col][:end] verwendet wurde.

String Manipulation

Umwandlung einer Liste aus Strings in einen einzigen string

# step 0: create a list with many strings in it
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']

# Step 1: Transform the whole list into a single "string"
str(planets) # should output: "['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']"

# Step 2: Now, we replace all characters that are unnecessary - such as ' [ and ] -such that we return a whole string,
        # only separated by commas, with no whitespace in between them:
## "['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']"
n = str(planets).replace("'", "").replace('[', '').replace(']', '').replace(' ', '') # replace everthing by empty-strings
print(n) # Final output 
## Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune

Aufspaltung von Strings an einem spezifischen Punkt

timestamps = time_series_str_column.str.split("+", expand=True) 
    ### Erklärung der Inputs:
    # > "+" is the Buchstabe, at which we will split the string.
    # > "expand = True" says that we want to keep both parts of the string that was being split.

String halbieren

Wir definieren 2 functions, welche den string jeweils halbieren:

def splitstring_p1(value):
  string1, string2 = value[:len(value) // 2], value[len(value) // 2:]
  return string1

def splitstring_p2(value):
  string1, string2 = value[:len(value) // 2], value[len(value) // 2:]
  return string2

Konvertierung eines Strings in ein Date Time-Format

df['Date-Time'] = pd.to_datetime(df['Date-Time'], format='%Y-%m-%d %H:%M:%S')
df['Date-Time'] # check, if it worked?

Installierung von Packages

Installierung Packages via conda

Conda ist ein sogenannter “Package Manager”, der vor der Installation eines gewünschten Pakets jeweils prüft, welche Abhängigkeiten erforderlich sind, damit das Paket verwendet werden kann.

  • Beipsiel: Wenn man zum Beispiel das Package sktime verwendet, dann kann conda erkennen, dass es einen Konflikt mit der aktuellen Version von - beispielsweise - dem Package Tornado geben könnte. Daher wird conda nicht nur sktime herunterladen, sondern auch das Paket Tornado auf eine neuere Version bringen, damit es mit sktime kompatibel wird.
  • Hinweis: Eine Installation via conda kann manchmal relativ lange dauern, da - insbesondere bei grossen Projekten - viele Abhängigkeiten überprüft werden müssen!
  • Wichtig: Für die Verwendung von conda sollte auf das Terminal zurückgegriffen werden.

Beispiel zur Verwendung conda’s via einem “Virtual Environment”
Um ein bestimmtes Package - z.B. sktime - in eine bestehende virtuelle Umgebung namens "myenv" zu installieren, muss folgender Code ins Terminal eingegeben werden:

conda install --name myenv sktime

Beispiel zur Verwendung conda’s via einem “Global Environment”
Um das Paket sktime in die aktuelle (globale) Umgebung (= dh “normal”), muss folgender Code ins Terminal eingegeben werden:

conda install sktime

Installierung von Packages via pip

Was sind die Nachteile von der Installation von Packages via pip statt einer Installation via conda?
pip prüft nicht im Hintergrund, ob die verschiedenen Versionen der verschiedenen Packages, die man verwendet, kompatibel sind. Daher kann die Verwendung von pip ein Projekt “kaputt machen”, wenn man ein bestehendes Package aktualisiert ODER ein neues Package installiert…

Um ein Paket mit pip zu installieren, muss folgender Code ins Terminal eingegeben werden:

pip install myPackage

Installierung von Packages // Modulen oder Sub-Modulen

import numpy as np # import eines Package // Modules
import matplotlib.pyplot as plt
from matplotlib.dates import date2num # das ist ein Beispiel zum import eines "Sub-Modules".
import pandas as pd
from scipy import stats
from datetime import datetime

De-Installierung von Packages via pip

pip uninstall fbprophet prophet cmdstanpy

Hilfe & Dokumentation gesucht?

help()

Umgang mit Pfaden

Warum ist der Umgang mit Pfaden so wichtig?
Weil man im Verlauf eines Data-Science Projektes stets Modelle, DFs oder Dateien laden ODER speichern muss. Für den Umgang mit Pfaden empfiehlt sich, das Package os zu beherrschen, welches bereits in “Base-Python” eingebaut ist.

Die Hauptformate, in denen Python-Files geladen & gespeichert werden, sind:

  • Das .csv-Format.
  • Das .pkl-Format.
  • etc…
    • Es gibt etliche andere Formate, aber die das .csv & .pkl sind diejenigen Formate, auf welche man sehr häufig antrifft

Setze den Pfad, an dem dein Computer die Daten / trainierten Modelle speichern soll

# Read data // Load data
import os # this is the library that can handle paths

save_folder = os.path.expanduser(os.path.join("~", # our User's "home"-directory --> for my Mac it is: "/Users/jomaye" 
                                              "Dokumente", # Next, we jump 1 directory called "Dokumente" further below
                                              "Programming")) # Allgemeint: each string after a Komma is a new directory you can set. This can go on infinitively ;)


save_folder # check if it worked? --> yes! Now you see your path =)
## '/Users/jomaye/Dokumente/Programming'

Speichern von Daten


Speichern als .csv-Datei

dynamic_name = "name_of_a_specific_characteristic_of_the_variable_that_you_want_to_save" # this is needed for "dynamisches speichern" via F-String  
YOUR_VAR_NAME.to_csv(f"Name-of-the-CSV-{dynamic_name}.csv") # I use f-strings since it allows me to adapt the CSV-filenames to a specific characteristic, that I used for a model / method etc...

Lesen von Daten


Um Datensätze zu laden, benötigt man das Package pandas. In einigen Fällen können zusätzliche Packages erforderlich sein.

Laden einer Excel-Datei

epex_df = pd.read_excel("./Data_V2/Preis_aktuell_Spot_EEX_CH-19-ver-mac.xlsx", # plug in the correct path
                        header=[1], # The dataset Column-names beginnt ab 2. Zeile (--> 1. Zeile ist der Titel des Excel-Files)
                        sheet_name='Prices', # If you have more than 1 Excel-Sheet within the Excel-File, you need to specify
                        # which sheet you want to load
                        engine='openpyxl') # This input will (sometimes) be needed if you load data from an Excel-File via 
                                           # a Windows-Computer, otherwise it can print an error!
epex_df # output the ddset a

Laden einer .csv-Datei

test = pd.read_csv(
    "C:/Users/u235051/Downloads/ETS_Database_v38/ETS_Database_v38.csv", 
    sep='\t' # Im CSV-File waren die Spalten via "Tab"-Taste separiert, deshalb diese option zwingend anzugeben ist (ansonsten Error!)
) 
test # check, if it worked?

Laden von Daten via einer Webseiten-URL

Um auf einen als .csv-Datei gespeicherten Datensatz auf einer Website zugreifen zu können, müssen wir - neben pandas - ein zusätzliches Package namens requests verwenden.

Schritt 1:

Zunächst muss die .csv-Datei auf der Webseite angefagt werden. Dies erreicht man, indem man eine Abfrage an den Remote-Server- // Webseite sendet, auf der die .csv-Datei gespeichert ist.

import requests # load the library needed

download_url = "https://raw.githubusercontent.com/fivethirtyeight/data/master/nba-elo/nbaallelo.csv" # absolute URL
target_csv_path = "nba_all_elo.csv" # name of the .csv-file that contains the data

response = requests.get(download_url) # using an API that "gets" (= http-protocol language) the data from the server
response.raise_for_status()    # Check that the request was successful
with open(target_csv_path, "wb") as f:
    f.write(response.content)
print("Download ready.")

Schritt 2:

Nachdem man Zugriff auf die .csv-Datei bekommt, müssen die Daten nun via pandas geladen werden.

import pandas as pd # load the library needed

nba = pd.read_csv("nba_all_elo.csv") # load the data --> ddset is called 'nba'

type(nba) # check if it worked? --> should output: <class 'pandas.core.frame.DataFrame'>

Nun kann ein erster Blick auf den DataFrame geworfen werden:

nba.head()

Laden einer Pickel-Datei

filename = "Put_Your_Pickle-File_Name_Here" # Alternativ: os.path.join(data_folder_variable_where_pkl_is_saved, filename)
test_loaded_pkl =  pickle.load(open(filename, 'rb'))

DataFrame erstellen


df = pd.DataFrame({'LoadCH': ts_loadFc[time_index_hourly], 
                   'LoadD': ts_DE_loadFc.resample('H').mean()[time_index_hourly], 
                   'LoadF': ts_FR_loadFc[time_index_hourly], 
                   'LoadIT': ts_IT_loadFc[time_index_hourly],
                   'GenCH': ts_genFc[time_index_hourly], 
                   'GenD': ts_DE_genFc[time_index_hourly], 
                   'GenF': ts_FR_genFc[time_index_hourly], 
                   'GenIT': ts_IT_genFc[time_index_hourly],
                   'RenGenCH': ts_RenGenAllCH[time_index_hourly], # we take the aggregated renewable generation
                   'RenGenD': ts_RenGenAllDE[time_index_hourly], 
                   'RenGenF': ts_RenGenAllFR[time_index_hourly], 
                   'RenGenIT': ts_RenGenAllIT[time_index_hourly], 
                   'TransFromDach': ts_TransFromDACH[time_index_hourly], 
                   'TransToDach': ts_TransToDACH[time_index_hourly], 
                   'TransToIT': ts_TransToIT[time_index_hourly],
                   'SeasonAndProduct': ts_season_prod[time_index_hourly], # TO-DO: re-name this column, since its name is confusing!
                   'tInfoDaySin' : ts_timeInfoDayI_4forecast[time_index_hourly],
                   'tInfoDayCos' : ts_timeInfoDayII_4forecast[time_index_hourly], 
                   'tInfoYearSin' : ts_timeInfoYearI_4forecast[time_index_hourly], 
                   'tInfoYearCos' : ts_timeInfoYearII_4forecast[time_index_hourly],
                   'PricesCH': ts_DA_Price[time_index_hourly]}) 

Umwandlungen


Konvertierung einer Series zu einem Array

Series.to_numpy() # this also works on a column of a dataframe =)

Konvertierung einer Pandas Series zu einem DataFrame

import pandas as pd

s = pd.Series(["a", "b", "c"],
              name="vals")
s.to_frame()

Selektion & Filtrierung von Zeilen & Spalten


Kontext & Relevanz
Wenn man mit DataFrames via pandas arbeitet, wird eine der wichtigsten Anforderungen (beim Umgang mit Daten) sein, wie man bestimmte Spalten aus dem Datensatz selektiert UND wie man Teilmengen vom Datensatz ausgibt.

Für den Anfang - und um sich mit den gängigsten Techniken vertraut zu machen - empfehle ich es, sich dieses Youtube-Video anzusehen: Einsteiger-Tutorial zum Filtern & Auswählen von Spalten.

Führe die untere Notebook-Zelle aus, um die kommenden Beispiele ausführen zu können:

import pandas as pd # load package needed

file_path = './data/filter-and-selection/sample_orders.csv' # type in the path-location of your data
dd = pd.read_csv(file_path) # load the data

dd.head() # check if it worked? --> yes! --> should print the first 5 rows of your ddset
dd.info() # check also all data-types

Selektion 1 Spalte in einem DatenFrame

In diesem Beispiel wähle ich die Spalte order_id aus meinem Datensatz dd aus:

print(
    dd['order_id'] # take the dataframe 'dd' and print me only the column called 'order_id' --> this is a subset
)

Möglichkeit 2: Selektion einer Spalte via dot-notation

Ein Nachteil der dot-notation-Methode ist, dass sie nicht funktioniert, WENN die Spalten Whitespaces enthalten!

test = dd.order_id # ACHTUNG: funktioniert nicht, wenn es Leerschläge gibt!!
print(test)

Selektiere mehrerer Spalten in einem DataFrame

Ähnlich wie bei R, muss ein “Set” an den Spaltenselektor übergeben werden.

print(
    dd[['order_id', 'order_total']] # take the dataframe 'dd' and print me only the columns called 'order_id' && 'order_total'
)

Selektion UND Filter von Zeilen via 1 Bedingung innerhalb eines DataFrames

Angenommen, wir wollen die Zeile mit order_id == 1004 auswählen:

print(
    dd[
        dd['order_id'] == 1004 # note that this INNER bracket will run a function that searches through and would only print 
                               # a Boolean-List of "True" or "False" for all rows  --> example-video: ab 2:36-3:34 --> https://www.youtube.com/watch?v=htyWDxKVttE
    ]                          # This outer selector will tell Python: "Select" only the row where the column 'order_id' == 1004
)

# short-version:

print(dd[dd['order_id'] == 1004])

Selektion UND Filter von Zeilen via mehreren Bedingungen innerhalb eines DataFrames

Nun wollen wir die Zeilen auswählen, bei denen "order_total" >= 50.00 UND "order_date" == 12/1/17 erfüllt sind. Im Grunde ist es dasselbe, wie bei der Auswahl & Filtrierung mit einer Bedingung, mit dem Unterschied, dass hier die INNEREN Klammern noch mit einer zusätzlichen (...) Klammer (für jede Bedingung!) umgeschlossen werden.

print(
    dd[
        (dd['order_total'] >= 50) & (dd['order_date'] == '12/1/17') # in contrast to **one condition**, we just wrap up a (...) for each condition
    ]                          # This outer selector will tell Python: "Select" only the row where the column 'order_id' == 1004
)

Alternative mit "OR": Statt "AND" zu verwenden, kann - zum Beispiel - auch die "OR"-Bedingung verwendet werden.

print(dd[(dd['order_total'] >= 50) | (dd['order_date'] == '12/1/17')])

Selektiere nur Zeilen mit Missing-Values

null_data = df[df.isnull().any(axis = 1)] # this will only select the rows that contain at least one missing-value
null_data # check, if it worked?

Selektion mittels “Accessor Operators” iloc & loc


Das Package pandas verwendet sogenannte Accessor-Operators, um DFs zu filtern. Es gibt 2 Arten von Accessor-Operators:

  • iloc (= basierend auf der Postition (= Nummer) des jeweiligen Index der Zeile oder Spalte),
    • Hinweis: Die Reihenfolge der Inputs, wenn man iloc verwendet, sind:
      1. ‘Zeilen’, und dann
      2. ‘Spalten’.
  • loc (= basierend auf die Index-Namen der Zeilen und Spalten des DataFrames).
    • Hinweis: TDie Reihenfolge der Inputs, wenn man loc, ist:
      1. ‘Zeilen’, und dann
      2. ‘Spalten’.

Beispiel: Selektion 1ster Zeile und 5ter Spalte mittels iloc

Um iloc verwenden zu können, muss man die Position des Zeilen- und Spalten-Index kennen.

  • Hinweis: iloc & loc werden oft verwendet, um einen bestimmten Wert in einem Datensatz zu selektieren. Allerdings können loc und iloc auch ganze Zeilen // Beobachtungen selektieren und nicht nur einen bestimmten Wert innerhalb einer Zeile.
test = dd.iloc[0,4] # Reihenfolge der inputs == 1) 'rows' (--> "0" == 1st row), then 2) 'columns' 
                    # (--> "4" == 5th column)
print(test) # check if it worked? --> should print the value '65'

Beispiel: Selektion 1ster Zeile und 5ter Spalte mittels loc

Um loc verwenden zu können, muss man die Namen der Spalten und Zeilen kennen.

  • Wichtiger Hinweis:
    • Der Standard-Name von Zeilen innerhalb eines DFs sind einfach gewöhnliche Zahlen-Abfolgen, also zum Beispiel 0,1,2...,10,11,12,....
    • Und nun kommt das Verwirrendste: der Index einer Zeile sind EBENFALLS Zahlenabfolgen, wie 0,1,2...,10,11,12,...!
      • Daher ist es in der Regel so, dass loc und iloc DENSELBEN ersten Input haben, um Zeilen zu selektieren! 🤓
test = dd.loc[0,'order_total'] # Reihenfolge der inputs == 1) 'rows' (--> "0" == NAME der 1st row), 
                               # then 2) 'columns' (--> "order_total" == NAME der gewünschten 5th column)
print(test) # check if it worked? --> should print the value '65'

Beispiel: Selektion mittels Schwellenwert via loc

Diese Selektionstechnik kann nützlich sein, wenn man einige Werte in bestimmten Spalten ersetzen möchte mit Schwellenwerten (also von einer kontinuierliechen Skala zu einer diskreten Skala wechselt…) → siehe ‘Ausreisser’-Kapitel

# Only display all rows, where the 'pressure'-column is > than the threshold of 1051 bar

df_weather.loc[df_weather.pressure > 1051, 'pressure']

Beispiel: Selektion aller Zeilen via iloc

#####
## Möglichkeit 1: selection of only 1 row

test_row1 = dd.iloc[0,] # Important: 1) rows, then 2) columns --> we want the entire 1st row, which includes ALL columns
                        # Note: das Komma NACH dem "0" zeigt an, dass wir ALLE columns selektieren wollen!

#####
## Möglichkeit 2: selection of > 1 row --> notice the additional wrap with [...] WITHIN iloc[]!

test_multiRow = dd.iloc[[0,1,2,3,5,8],] # '[0,1,2,3,5,8]' will select the '1st, 2nd, 3rd, 4th, 6th and 9th' row
                                            # while also selecting ALL columns simultaneously

#####   
## check if it worked? --> yes!
print(test_row1) # should print only 1 row BUT with ALL the column --> weird output, because the columns sind abgebildet als rows xD
print(test_multiRow)

Beispiel: Selektion aller Zeilen via loc

Tipp: Von allen Optionen, empfehle ich “Möglichkeit 3” in den unteren Beispielen!

#####
## Möglichkeit 1: selection of only 1 row

test_row1 = dd.loc[0,] # das Komma NACH dem "0" zeigt an, dass wir ALLE columns selektieren wollen!

#####
## Möglichkeit 2: selection of > 1 row --> notice the additional wrap [...] WITHIN loc[]!

test_multiRow = dd.loc[[0,1,2,3,5,8],] # Weil - per default - die 'row-labels' (= name des Indexes 
                                            # der Zeilen) dieselben sind, wie die Position, ist der Code 
                                            # für 'loc' derselbe, wie für 'iloc' hier...
        
#####
## Möglichkeit 3: Beste & schönste Solution (meiner Meinung nach!)
rows = list(range(0,16)) # will create a list that goes from 0 to 99 --> this will be for the row-labels
columns = ['order_id', 'order_date', 'order_total'] # this will be for the column-labels
                                                    # Pro-Tipp: columns = list(data.columns)
df = dd.loc[rows, columns]
        
        
#####   
## check if it worked? --> yes!
print(test_row1) # should print only 1 row BUT with ALL the column --> weird output, because the columns sind abgebildet als rows xD
print(test_multiRow)
print(df)

Ersetze Werte innerhalb einer Spalte

df.loc[df.ungewichtet > 1, 'ungewichtet'] = 1 # hier werde ich alle Werte der Spalte "ungewichtet" > 1 mit dem Wert "1" ersetzen!

Die Unterschiede im Slicing beim Verwenden von iloc VS. loc

Merke
Wenn man iloc verwendet, wird der Bereich 0:5 die Einträge 0,...,4 selektieren, d.h. es wird hier EXKLUSIV indiziert. Auf der anderen Seite indiziert loc hingegen INKLUSIV. Also wird der gleiche Bereich 0:5 die Einträge 0,...,5 auswählen!!!

Wenn man also mit loc und iloc den GLEICHEN Bereich selektieren möchte, müssen man die range()-Funktion entsprechend leicht anpassen.

Beispiel:

## Möglichkeit 1: with 'iloc'
iloc_test = dd.iloc[0:5,0] # row-position == 0:5 --> first 5 rows; EXCLUDES '5' from the range "0,1,2,3,4,5" 
                                          # --> hence range(0:5) results in --> "0,1,2,3,4"
                                          # column-position == 0 --> 1st row --> remember: indexing in 
                                          # Python starts at '0'!

    # IMPORTANT: 'iloc' uses the 'Python stdlib' indexing scheme, where the first element of the range is 
    # included and the last one excluded. So 0:5 will select entries 0,...,4 (= these are the first *5* 
    # entries!!).

## Möglichkeit 2: to get the SAME output with 'loc', we need a slightly DIFFERENT range!
loc_test = dd.loc[0:4,'order_id'] # row-position == 0:4 --> first 5 rows; INCLUDES '4' 
                                  # --> hence range(0:4) results in --> "0,1,2,3,4"

## check if the output are the same, even though "range()" has slightly different inputs? --> yes!
print(iloc_test)
print(loc_test)

Sortierung & Filtrierung


Sortierung der (Pearson-) Korrelation vom höchsten zum tiefsten Wert?

### Find the correlations' ranking for the day-ahead electricity price and the rest of the features:

# Step 1: Create a Pearson-Korrelation Matrix out of your dataframe:
correlations = df.corr(method='pearson') # the variable 'correlations' is a dataframe!

# Step 2: use 'sort_values' to sort the column from "most important" (highest value) to "least important":
print(correlations['pricesCH'].sort_values(ascending=False).to_string())

Beispiel: Selektiere nur die höchste Korrelation und - anschliessend - sortiere die Werte und - schliesslich - verwendet print(), um diese anzuzeigen

highly_correlated = correlations[correlations > 0.75]
print(highly_correlated[highly_correlated < 1.0].stack().to_string())

Eine simple Daten-Exploration


Warum ist eine Daten-Exploration (vor dem Modellierungs-Prozess) so wichtig?
Nachdem man Python angewiesen hat, den Datensatz zu lesen, ist das Erste, was man tun sollte, sich (sehr) intensiv mit dem Datensatz vertraut zu machen. Das ist Schlüssel, sonst wird man nicht in der Lage sein, eine gute Datenanalyse durchzuführen!

Ein gutes Beispiel, um die Relevanz von Daten-Explorationen zu unterstreichen, wäre meine Masterarbeit: dort habe ich beinahe 3 Wochen damit verbracht, meinen Datensatz zunächst kennenzulernen. Beispielsweise, waren die Spalten im Datensatz mit kryptischen Abkürzungen bezeichnet, was eine Modellierung zunächst verunmöglicht hätte!

Anzahl an Zeilen herausfinden

len(nba) # to get the number of observations // rows 

    # note: 'nba' is the name of the ddset

Anzahl Zeilen & Spalten herausfinden

nba.shape # to get number of rows AND columns

    # note: 'nba' is the name of the ddset

Gesamter Datensatz ansehen

nba # just type in the name of the variable in which your dataframe is stored in --> 'nba' is the name of your dataframe here

Nur die ersten 5 Beobachtungen des Datensatzes ansehen

nba.head() # head() is often used to check whether your dataset really contains data you care about 

    # here we check: does the ddset really contains data about the NBA?

Hinweis: Wenn man viele Spalten im Datensatz hat, wird Python sie nicht alle anzeigen, wenn man diese mit head() abbilden möchte Allerdings können in pandas die Standard-Einstellungen angepasst werden:

pd.set_option("display.max.columns", None) # this will tell Python: "show me ALL the columns!"

nba.head() # execute 'head()' again to check if the setting changed correclty? --> yes!

Nur die letzten 5 Beobachtungen des Datensatzes ansehen

nba.tail() # View last 5 rows

# Viewing very specific rows is also possible: 
nba.tail(3) # Here, we view the last 3 rows

Zusammenfassung des Datensatzes

Es ist zu bemerken, dass - für die Abbildung von “Summary-Statistiken” - das zusätzliche Package numpy benötigt wird.

import numpy as np # load numpy for summary-statistics

nba.describe().round(2) # results will be rounded onto 2 digits

Hinweis: Wenn der Datensatz Spalten vom Typ Object enthält, benötigen Sie eine etwas andere Version der Funktion describe(), damit man “Summary Statistics” anzeigen kann.

nba.describe(include=object) # if you have some weird columns being of the type 'object'

Nur unique()-Werte innerhalb der Spalten anzeigen lassen

Warum benötigt man unique()? Unique() kann nützlich sein, wenn man alle eindeutigen Kategorien innerhalb einer Spalte filtrieren möchte. Man kann diese “unique categories” dann innerhalb einer neuen (Listen-) Variable abspeichern, um - mit Hilfe eines For Loops - eine Funktion auf die jeweiligen Kategorien anzuwenden.

gapminder['continent'].unique()

Median & Durchschnitt für eine bestimmte Spalte berechnen

Für den Durchschnitt, wird folgender Code benötigt:

mean_points = reviews.points.mean() # calculate the mean of the column 'points' within the ddset 'reviews'

Für den Median:

median_points = reviews.points.median() # calculate the median of the column 'points' within the ddset 'reviews'

Anwendung einer Custom-Funktion f(x), aber nur auf bestimmte Zeilen im Datensatz mittels numpy

import numpy as np # In order to be able to perform a transformation, we will need the `numpy`-package

# set-up:
x = np.arange(10) # this is our column 'x' --> Output of this: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
condlist = [x<3, x>5] # set of conditions, that need to be fullfilled --> which are the value-ranges, on which you will apply 
# a custom-function [which will be defined next]? --> all numbers below 3 AND all numbers above 5
choicelist = [x, x**2] # the custom-function you will apply here: x^2

# output:
np.select(condlist, choicelist, default=np.nan) # apply x^2 on: x < 3 AND x > 5

Daten-Cleaning


Finde den Data Type einer Variablen heraus UND die Anzahl an Missings in einer Spalte pro Spalte

Mit dem folgenden einfachen Code können wir herausfinden, ob die Spalten vom Typ integer, string, boolean etc. sind.

nba.info() # this will output all the types of each column in your dataset & how many NON-missings you have per column

    # note: Pay attention to any columns that are from the type 'object'! --> lese bemerkung unten...

Um einen schönen Überblick über alle Data Types zu erhalten, die es in Python gibt, empfehle ich, die Website von W3Schools.com zu besuchen

Hinweis: Es kann sein, dass Spalten vom Typ object sind. In der Praxis bedeutet das oft, dass alle Werte in einer object-Spalte vom Typ string sind. Wenn man auf object-Spalten stösst, wird dringend empfohlen, sie in einen geeigneteren Data Type zu konvertieren, da sonst einige der Funktionen auf object-Spalten nicht funktionieren…

Finde den Data Type aller möglichen Objekte in Python:

x = 5
print(type(x))

Lösche 1 ODER mehrere Spalten

Wichtige Syntax bei der Zeilen- & Spalten-Bezeichnung
Beachte, dass axis = 1 die Spalten bezeichnet, während axis = 0 die Zeilen bezeichnet.

df_energy = df_energy.drop(['tInfoDaySin', 'tInfoDayCos', 'tInfoYearSin', 'tInfoYearCos'], axis=1)

Umbenennung von 1 ODER mehrerer Spalten

df2 = df.rename({'oldName1': 'newName1', 'oldName2': 'newName2'}, axis='columns') # not all columns have to be renamed, only those with a new name

Setze ODER lösche den Row-Index mittels .reset_index() und .set_index()

df_energy = df_energy.set_index('Date-Time') # to set the index
df_energy = df_energy.reset_index() # to reset the index --> df.reset_index(drop= True) will drop the index, which would 
                                    # otherwise become a new column instead of just dropping it!

Erstelle eine neue Spalte mit unique IDs

  • For a single Column:
df['id'] = df.groupby(['date']).ngroup() # of Course, you could simply use 1 column fo the assignment of an unique ID. 
  • For multiple Columns:
df['id'] = df.groupby(['LastName','FirstName']).ngroup() # here, we use the column 'LastName' & 'FirstName' together, to create 
# a unique ID.

# Quelle: https://stackoverflow.com/questions/45685254/q-pandas-how-to-efficiently-assign-unique-id-to-individuals-with-multiple-ent

Wende eine function auf eine Spalte im DataFrame an

Dies kann nützlich sein, wenn man eine Spalte eines DataFrames umwandeln muss:

# Step 1: Define any function, that you will need to be applied, in order to transform 

def my_function():
  ... # hier kommen die verschiedenen Anweisungen
  ... # hier kommen die verschiedenen Anweisungen
  return some_variable

# Step 2: Apply this newly specified function on your column of your data-frame, that you wish to transform

df['new_column'] = df['column_1'].apply(my_function)

Umgang mit Missings


Zähle die totale Anzahl an Missings innerhalb des DataFrames

print('There are {} missing values or NaNs in df_energy.'
      .format(df_energy.isnull().values.sum()))

Zähle die Missings pro Spalte

df_energy.isnull().sum(axis=0) # outputs the number of NaNs for each column

Zähle alle Non-Missings, aber NUR, falls die Spalten keine 0-Werte enthalten

Achtung: der “Code-Trick” unten funktioniert nur, wenn die Spalten keine Werte enthalten, die ‘0’ sind! Das liegt daran, dass die Zahl ‘0’ - als boolean - als False ausgegeben wird, und wir daher die FALSCHE Anzahl fehlender Werte erhalten würden!

Daher sollte man den folgenden Code nur verwenden, WENN man sichergestellt hat, dass meine (zu analysierenden) Spalten KEINE “0”-Werte enthalten!

# Display the number of non-missing values in each column

print('Non-zero values in each column:\n', df_energy.astype(bool).sum(axis=0), sep='\n')

Da die obige Notebook-Zelle nur die korrekte Anzahl an Missings ausgibt, WENN die Spalte keine “0”-Werte enthält, habe ich einen Code aufgelistet, der zählt, wie viele “0”-Werte in jeder Spalte enthalten sind:

(df_energy == 0).astype(int).sum(axis=0) # count the numbers of '0s' in each column [axis = 0, for columns...]

Zeige nur diejenigen Spalten, die Missing enthalten

# Display the rows with null // missing values:

df_energy[df_energy.isnull().any(axis=1)].tail()

Ersetze alle Missings mit 0-Werten

df['ColumnWithMissings'] = df_tot['ColumnWithMissings'].fillna(0) # replaces all missing-values within the column with 0s. 

Ersetze alle Non-Missings in einer Spalte mit Missings

Nehmen wir an, wir verüfgen über eine Zeitreihe, die einen Zeilen-Index mit Zeit-Daten entählt!

### Step 1: define the range in which you want to replace values
start = '2020-01-01' # 01. Januar 2020 --> ab hier wollen wir die values der Time-Series mit Missings ersetzen
stop = '2020-08-01' # 01. August 2020 --> bis zu diesem Datum sollen die Missings eingefügt werden

### Step 2: replace the values with missings via the ".loc[row-indexer, column-indexer]"
df.loc[start:stop, 'y_hat'] = None # This will replace all the values within the 'y_hat'-column - in the range from 
                                   # 01.01.2020-01.08.2020 with Missing-values (instead of "normal"-values)

Missing-Imputationen


Missing Interpolation für Zeitreihen

# Fill null values using interpolation:

df_energy.interpolate(method='linear', limit_direction='forward', inplace=True, axis=0) # since we have 

Duplikate in den Daten


Zähle alle Duplikate im DataFrame

temp_energy = df_energy.duplicated(keep='first').sum()

print('There are {} duplicate rows in df_energy based on all columns.'
      .format(temp_energy))

Lösche Duplikate

# Variante 1: mit reset_index & neuer set_index
df_weather_2 = df_weather.reset_index().drop_duplicates(
    subset=['time', 'city_name'], # Drop the duplicate, if all the rows are the same // have the same 
                                  # values (Achtung: we only look at the duplicates in the 'time' & 
                                  # 'city_name'-column from this analysis!).
    keep='last').set_index('time') # if you have duplicate, keep only the last of the duplicated-rows.
# Variante 2: man dropt den "alten" Index (VOR merge) und neuem set_index 
df_unique_dates = df_ferien.drop_duplicates(
    subset='datum', # only consider the column "datum" [= column that has duplicates] when dropping the duplicates 
    keep='first').reset_index(drop=True) # reset the index, otherwise you get weird indizes (mit 10'000 für manche)
                                         # 'drop = True' means that we do not keep the "old" index as a separate 
                                         # 'column'
df_unique_dates

Ausreisser


Zeichne einen Boxplot

import seaborn as sns

sns.boxplot(x=df_weather['pressure'])
plt.show()
  • Schlüssel-Frage:

    Gibt es Ausreisser? → Ja / Nein

Der “Trick”, um die obige Frage zu beantworten, lautet:
Mache eine “Summary Statistics” mit deinen Daten und - falls du es z.B. mit Temperatur-Daten zu tun hast - dann solltest du mal googlen: “was sind die Höchst-Temperaturen auf der Erde?”, und anschliessend auf Wikipedia gehen, um zu beurteilen, ob die Daten-Werte in der Spalte “Temperatur” auch tatsächlich realistisch sind ODER ob diese Ausreisser enthält?

  • Beispiel:
    Outlier Examples

Selbst ein Druck von etwa 100'000 hPa oder 10 MPa, der in der obigen Abbildung deutlich sichtbar ist, entspricht einem Wert, der größer ist, als der atmosphärische Druck der Venus. Entsprechend können wir nun jeden Wert in der Spalte “Druck” als “NaN”, der höher als 1051 hPa ist, was knapp über dem höchsten Luftdruck-Wert liegt, der jemals auf der Iberischen Halbinsel gemessenen wurde. Während Ausreißer auf der niedrigen Seite im obigen Boxplot nicht sichtbar sind, ist es eine gute Idee, auch dennoch diejenigen Werte zu ersetzen, die kleiner als 931 hPa sind, was dem niedrigsten - jemals auf der iberischen Halbinsel - aufgezeichneten Luftdruck-Wert entspricht.

Schritt 2:
Wenn die Antwort auf die obige Frage “ja” lautet, dann ersetzt man diejenigen Werte, welche über dem festgelegten Ausreisser-Wert liegen, auf NaNs, damit diese in der späteren Modellierung & bei der Analyse nicht berücksichtigt werden (denn diese würden die Ergebnisse verfälschen).

# Replace outliers in the `Set_Name_of_Column_with_the_Outlier_hier`-column with `NaN`s

df_weather.loc[df_weather.pressure > 1051, 'pressure'] = np.nan
df_weather.loc[df_weather.pressure < 931, 'pressure'] = np.nan

Das Merging & Splitting von Datensätzen


Wichtiger Hinweis zu Merging:
Wenn man 2 DataFrames mergen (= zusammenführen) möchte, von denen Einer kleiner ist, als der Andere, dann wird es NICHT möglich sein, den größeren Datensatz mit merge() kleiner zu machen! Ich habe eine Menge Zeit damit verbracht, bisher aber leider ohne Erfolg. Es gibt allerdings dennoch eine Alternative-Lösung zu diesem Problem: man muss nur die Duplikate des größeren DataFrames löschen (siehe Kapitel Duplikate, um den Datensatz dennoch kleiner zu machen =)

Aufsplittung eines grossen Datensatzes in einen kleineren Datensatz via Kategorien

# Split the df_weather into 5 dataframes (one for each of the 5 cities):

df_1, df_2, df_3, df_4, df_5 = [x for _, x in df_weather.groupby('city_name')]
dfs = [df_1, df_2, df_3, df_4, df_5]

Merging von 2 Datensätzen via ihrem Row-Index

Relevanz?:
Man kann diese Art von Merging nur verwenden, WENN beide DFs den gleichen Zeilen-Index haben! Mit dieser Art von Merging, lassen sich neue Spalten aus einem anderen DF hinzufügen!

# Let's merge all the y- & X-Variables from the training-set together:
test = pd.merge(
    y_train, # der Trainingsdatensatz für die y-Variable
    x_train, # der Trainingsdatensatz für alle X-Variablen
    how="outer",
    left_on=y_train.index, # merging via index // row-label des DataFrames der y-Variable
    right_on=x_train.index, # merging via index // row-label des DataFrames der x-Variablen
).set_index('key_0') # optional: da wir hier eine Zeitreihe haben, dessen Row-Index den Column-Name 'key_0' animmt 
                     # beim Merging, wird die Spalte 'key_0' hier als neuer Row-Index für den gemerged Dataframe 
                     # gesetzt.
test # check if it worked

Gleichzeitiges Merging von mehreren Datensätzen

Ausgangssituation:
Nehmen wir an, dass wir - zunächst - 2 Datensätze haben, einen für das Wetter und einen für die Energiepreise. Außerdem handelt es sich bei diesen beiden Datensätzen um Zeitreihen. Daher haben sie den gleichen Zeitindex (= Zeilenbezeichnung), formatiert in UTC.

  • Schritt 1: Aufsplittung des Wetter-Datensatzes, sortiert nach Städten…
# Split the df_weather into 5 dataframes (one for each of the 5 cities):

df_1, df_2, df_3, df_4, df_5 = [x for _, x in df_weather.groupby('city_name')]
dfs = [df_1, df_2, df_3, df_4, df_5]
  • Schritt 2: Merging der 5 Teildatensätze mit dem Energie-Preis-Datensatz…
# Step 1: save a copy, in case you do a wrong merging!
df_final = df_energy 

# Step 2: make a for-loop, to merge all the 6 datasets simultaneously
for df in dfs: # here, we loop through every city-group of our list of data frames (see step 1)
    city = df['city_name'].unique() # we store the names of the 5 cities - as a list - in a variable
    city_str = str(city).replace("'", "").replace('[', '').replace(']', '').replace(' ', '') # we perform some 
    # string-manipulation to eliminate all the characters that are not necessary
    df = df.add_suffix('_{}'.format(city_str)) # we re-name the columns, by adding the name of the city to each column.
    df_final = df_final.merge(df, # this is the merging-part!
                              on=['time'], # we want to merge via the index // row-label of both datasets --> since 
                                           # they are both in UTC-time, this will work!
                              how='outer') # 'outer' means: we want the 'union' --> see this youtube-video for a 
                                           # good explanation: https://www.youtube.com/watch?v=h4hOPGo4UVU
    df_final = df_final.drop('city_name_{}'.format(city_str), axis=1) # let's drop some columns that we don't need anymore
    
# Step 3: "final results"-check
df_final.columns # show the merging-results, by displaying all the column-names --> DONE! =)

Schritt 3: Führe abschliessende Checks durch

  • Schlüssel-Frage:

    Hat das Merging nun tatsächlich geklappt? → Ja / Nein ?


    • "Trick", um diese Frage zu beantworten: Schaue auf die Missings & Duplikate.
# Display the number of NaNs and duplicates in the final dataframe

print('There are {} missing values or NaNs in df_final.'
      .format(df_final.isnull().values.sum()))

temp_final = df_final.duplicated(keep='first').sum()

print('\nThere are {} duplicate rows in df_energy based on all columns.'
      .format(temp_final))

Abschliessende Beurteilung zur Frage: “hat das Merging nun tatsächlich geklappt”?

  • Wenn man keine Missings UND keine Duplikate hat, dann sollte das Merging funktioniert haben und die Antwort auf die obige Frage sollte “ja” lauten.
  • Wenn die Antwort auf die obige Frage “Nein” lautet, muss man zu Schritt 2 zurückkehren und versuchen herauszufinden, was beim Merging der Datensätze falsch gemacht wurde.

Skalierung von Variablen


Bei der Anwendung von maschinellem Lernen im Bereich des Deep Learnings, lautet eine Voraussetzung, um solche Modelle nutzen zu können, dass wir unsere Variablen zunächst skalieren.

Die üblichen Methoden zur Skalierung von Variablen können auf diesem Stack-Exchange Post gefunden werden.

Normalisierung

Wenn man eine Variable “normalisiert”, dann bedeutet das, dass der Bereich der möglichen Werte zwischen 0 und 1 liegt (= wird auch “Definitionsbereich” in Mathe genannt).

Skaliere alle X-Variablen und die Y-Variable

from sklearn.preprocessing import MinMaxScaler # Der Name der Klasse für Normalisierung = 'MinMaxScaler' (--> mega weird xDD)

# step 1: initialize the Class
scaler_X = MinMaxScaler(feature_range=(0, 1))
scaler_y = MinMaxScaler(feature_range=(0, 1))
# step 2: wende den Scaler uf die X- & Y-Variablen (dabei nur auf das Training-Set) an (Achtung: funktioniert nur auf numy-arrays, 
# aber nicht auf Series // Data-Frame Columns!)
scaler_X.fit(X_compl_df[:train_end_idx]) # alternativ: scaler_y.fit(X_train.to_numpy().reshape(-1, 1))
scaler_y.fit(y_compl_df[:train_end_idx]) # alternativ: scaler_y.fit(y_train.to_numpy().reshape(-1, 1))
# step 3: nach dem "fit" haben wir die Werte noch nicht als "DataFrame"-Typ gespeichert, weshalb wir nun noch 'transform' anwenden
X_norm = scaler_X.transform(X_compl_df)
y_norm = scaler_y.transform(y_compl_df)

Fortgeschrittene Daten-Exploration


Erstelle eine Korrelations-Matrix via einem DataFrame

# Step 1: construct a 'Pearson Correlation Matrix' as a Dataframe:
correlations = df.corr(method='pearson')

# Step 2: Load Libraries & plot Pearson correlation matrix:
import matplotlib.pyplot as plt
import seaborn as sns

fig = plt.figure(figsize=(24, 24)) #
sns.heatmap(correlations, annot=True, fmt='.2f') # import seaborn as sns
plt.title('Pearson Correlation Matrix')
plt.show()

Exploration von Zeitreihen-Daten


Indexierung

Wenn man es mit irgendwelchen “Zeit”-Variablen zu tun hat, muss man bei der Datenbereinigung in Python häufig auf sogenannte Date-Time Objects zurückgreifen.

Erstelle einen Zeitreihen-Index

start = '2020-01-01'
stop = '2021-01-01' # ACHTUNG: wird am 31.12.2020 enden
ts_index = pd.date_range(start, stop, freq = 'h', closed = 'left', tz = 'UTC')
ts_index

(Optionale) Erweiterung: Index-Slicing

Der Nachteil am obigen Index ist, dass er immer erst ab 0:00 Uhr beginnt. Das Problem dabei ist, dass man nicht immer einen Index benötigen wird, welcher punktgenau um 0:00 Uhr beginnt, sondern vielleicht einmal um 23:00 Uhr.

Die Lösung
Verwende die slicing-Technik, um ab der ersten Beobachtung vom Jahr 2020, um 00:00 zu starten, beispielsweise so für einen DataFrame: DF2 = DF1.loc[:'2019-05-26 13:00:00+00:00']. Im Falle einer series, würde das Slicing besipielsweise so aussehen: ts_index[4:].

Kopiere einen Index als separate Spalte

p_train = p_train.reset_index()
p_train['date'] = p_train['Date-Time']
p_train.set_index('Date-Time')

Wandle eine “Pseudo”-Zeitreihen-Spalte in eine Date-Time-Spalte um und setze diese dann als Index vom Datensatz

Wenn man mit Zeitreihen-Daten arbeitet, wird es oft vorkommen, dass man die Spalte “Datum” - welche oftmals vom Typ string ist - in eine Spalte vom Typ `Date-Time" umwandeln muss!

df_energy['Date-Time'] = pd.to_datetime(df_energy['Date-Time'], utc=True, infer_datetime_format=True)
df_energy = df_energy.set_index('Date-Time') # set the 'Date-Time'-Column as the index // row-label of our data-frame
Falls man das Format selbst setzen möchte:
#convert Delivery day to a date time column
epex_df['Delivery day'] = pd.to_datetime(epex_df['Delivery day'], format = '%Y%m%d')

Konvertiere eine Date-Time-Spalte in einen string

df['Date-Time'] = df['Date-Time'].apply(lambda x: x.strftime('%Y-%m-%d %H:%M:%S%z'))

Wieso würde man dies tun?

Manchmal werden wir auf das Problem antreffen, dass wir verschiedene Zeitreihen zusammenführen wollen, die aber unterschiedliche Date-Time-Formate haben!

Ein Beispiel von 2 Datensätzen mit unterschiedlichen Date-Time-Formaten:
Beispielsweise wäre die Spalte “Datum” im DataFrame_1 im Format “26-10-2022”, während die Spalte “Datum” im DataFrame_2 in einem unterschiedlichen Format, z.B. “2022-10-26”.

Daher müssen wir, BEVOR wir das universelle UTC-Datum-Zeit-Format anwenden, jeden einzelnen (separaten) DataFrame in das GLEICHE Datum-Zeit-Format bringen!

Da Python wirklich gut darin ist, eine solche Art von Transformationen im String-Format zu handhaben, können wir dies zu unserem Vorteil nutzen und:

1) Zunächst die einzelnen "Datum"-Spalten - <u>während alle noch im `string`-Format sind</u> - in ein Format bringen, welches **uniform** ist, über alle (separaten) `DataFrames` hinweg.
2) Anschliessend können die (separaten) `DataFrames` ins `UTC`-Format umgewandelt werden. 
3) Dann steht einem Merging nichts mehr im Wege! =)

Um zu sehen, wie man Strings manipuliert, empfehle ich stark, das Kapitel String-Manipulation in diesem Post zu lesen.

Erstelle eine range() von Datums

Beginn & Ende einer “Datums”-Periode festlegen, wobei die Standardfrequenz hier “täglich” ist.

import pandas as pd

pd.date_range(start='1/1/2018', end='1/08/2018')
  • Alternativ könnten wir es auch so machen:
# Step 1: We need a starting & endpoint for the range of dates
the_start_date = '2019-01-01'
the_end_date = str(datetime.today().date() + timedelta(days = 2)) # this is simply "today" + 2 days into the future 

# Step 2: Let's create the range of dates based on the starting- & end-point
date_range_of_interest = pd.date_range(the_start_date, # starting date-point (= is fixed)
                                         the_end_date, # end date-point (= can change dynamically // updates with new data from entsoe)
                                         freq = 'h', # we want to have 'hours'
                                         closed = 'left', # The input 'closed' let's you choose whether you want to include the start and end dates. 
                                         # --> 'left' will exclude the end-date, which is good, since this date is in the "future" (= today + 2)!
                                         tz = 'Europe/Berlin') # this is the time-zone

Mit 15min Zeitabständen

date_range_15Min = pd.date_range(the_start_date,
                                        the_end_date, 
                                        freq = '15Min', # only thing that changes --> will be used for Germany, since DE has quarterly-houred observations
                                        closed = 'left', 
                                        tz = 'Europe/Berlin')

Plotten einer Zeitreihe

Schritt 1:
Umwandlung der “Datum”-Spalte (im string-Format) in eine date-time-Spalte.

from datetime import datetime as dt # this is the package we need to convert a column into a 'date-time'

# Step 1: Define a function that is reading the date-column correctly

def parse_date(date):
    data=str(date)
    if date=="": # this condition will never be true, since the column 'Date-Time' NEVER has an empty string; 
    ### raise exception
        return None
    else:
        return pd.to_datetime(date, format='%Y-%m-%d %H:%M:%S', yearfirst = True, utc = True)

# Step 2: apply the above function on the Column 'Date-Time' to transform the column into a 'date-time'-type
raw_dd["Date-Time"] = raw_dd["Date-Time"].apply(parse_date)

Schritt 2:
Setze nun die Data Time-Spalte als Index des Datensatzes fest, ansonsten wird das Plotten nicht funktionieren.

# set the date-column as our row-label:
raw_dd = raw_dd.set_index("Date-Time")

Schritt 3:
Visualisierung der Zeitreihe

from sktime.utils.plotting import plot_series # use sktime and the 'plot_series'-module for this task

# Define our y-variable (= DA-Prices in CH):
y = raw_dd["pricesCH"] 

plot_series(y) # visual check if it worked? --> yes!

Bonus:
Ein alternativer Ansatz, um die Zeitreiehe zu visualisieren, könnte man die folgende Funktion verwenden:

# Define a function to plot different types of time-series:

def plot_series(df=None, column=None, series=pd.Series([]), # the 'series'-Input will be useful, if we choose to plot the series in a monthly- or weekly-frequency, instead of hourly...
                label=None, ylabel=None, title=None, start=0, end=None):
    """
    Plots a certain time-series which has either been loaded in a dataframe
    and which constitutes one of its columns or it a custom pandas series 
    created by the user. The user can define either the 'df' and the 'column' 
    or the 'series' and additionally, can also define the 'label', the 
    'ylabel', the 'title', the 'start' and the 'end' of the plot.
    """
    sns.set()
    fig, ax = plt.subplots(figsize=(30, 12))
    ax.set_xlabel('Time', fontsize=16)
    if column:
        ax.plot(df[column][start:end], label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if series.any():
        ax.plot(series, label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if label:
        ax.legend(fontsize=16)
    if title:
        ax.set_title(title, fontsize=24)
    ax.grid(True)
    return ax

Auto-Korrelation & Partielle-Autokorrelation

Die Visualisierung der partiellen Autokorrelation der Strompreis-Zeitreihen zeigt, dass die direkte Beziehung zwischen einer Beobachtung zu einer bestimmten Stunde (t) am stärksten mit den Beobachtungen zu den Zeitschritten t-1, t-2, t-24 und t-25 ist und danach abnimmt.

  • Schlussfolgerung aus der Visualisierung: Daher werden wir die 25 vorangegangenen Werte jeder Zeitreihe bei der Modellierung der Preisentwicklung verwenden.
# Step 1: load library
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # to plot 

# Step 2: Plot autocorrelation and partial autocorrelation plots
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(10, 6))
plot_acf(df_energy['pricesCH'], lags=50, ax=ax1)
plot_pacf(df_energy['pricesCH'], lags=50, ax=ax2)
plt.tight_layout()
plt.show()

Cross-Korrelation (Yt, Xt - k)

Es wäre definitiv vorteilhafter, wenn wir nur bestimmte vergangene Werte / Lags (Beobachtungen zu bestimmten vergangenen Zeitpunkten) einer bestimmten Variablen verwenden würden, basierend auf der Cross-Korrelation zwischen dem Strompreis und jedem anderen Variablen im Datensatz. Auf der unteren Graphik sehen wir - zum Beispiel - die Cross-Korrelation zwischen dem Strompreis und der erneuerbaren Energie-Angebot in der Schweiz. Wir sehen, dass es viele Zeitverschiebungen gibt, deren Korrelation nahe bei Null liegt und die weggelassen werden könnten.

from statsmodels.tsa.stattools import ccf # to plot the cross-correlation function

# Step 2: plot the cross-correlation between the day-ahead price "today" and with each of the first 50-lags of the column 'RenGen' 
cross_corr = ccf(df_energy['RenGenCH'], df_energy['pricesCH'])
plt.plot(cross_corr[0:50])
plt.show()

Feature Engineering


Rechnen (Addition & Subtraktion) mit Date-Time-Objekten


Wieso ist das Rechnen mit Date-Time-Objekten wichtig?
Bei Zeitreihen-Analysen wird es oftmals der Fall sein, dass man Zeitpunkte in der Zukunft oder in der Vergangenheit definiert. Daher ist es wichtig, dass man lernt, wie man Berechnungen im Zusammenhang mit Date-Time-Objekten durchführt, insbesondere auch dank dem Package datetime.

Genauer Zeitpunkt “jetzt” / “heute”

import datetime # Da wir mit "Zeit" arbeiten, brauche ich das Package "datetime"

jetzt = datetime.datetime.now() # current time

print(jetzt) # check: should output the current time

Addition, um zukünftigen Zeitpunkt zu berechnen, relativ zu “jetzt”

import datetime # Da wir mit "Zeit" arbeiten, brauche ich das Package "datetime"

jetzt = datetime.datetime.now() # current time
added_time = datetime.timedelta(hours= 1, minutes= 1) # anzahl an Stunden & Minuten, die du dazu addieren willst
ende = jetzt + added_time

print(ende) # check: should output "current time + 5h 2 min"

Subtraktion, um vergangenen Zeitpunkt zu berechnen, relativ zu “jetzt”

import datetime # Da wir mit "Zeit" arbeiten, brauche ich das Package "datetime"

fixe_zeit = datetime.datetime(2021, 7, 1, 8, 12) # datetime(year, month, day, hour, minute, second, microsecond), 
# wobei die ersten 3 Argumente (Jahr, Monat, Tag) OBLIGATORISCH sind! --> hier interessiert uns die letzten 2 Inputs, 
# nämlich die "8" und die "12" --> diese wiederspiegeln meine fixe 8h 12 min Arbeitszeit bei der SBB
added_time = datetime.timedelta(hours= 1, minutes= 1) # anzahl an Stunden, die du noch arbeiten musst
wie_lange_noch = fixe_arbeitszeit - added_time

print(wie_lange_noch.strftime("%H%M")) # check: should output 7h 11min --> yes!

Herausfinden, an welchem Wochentag (Mo / Di / Mi etc…) ein bestimmtes Datum war

Wenn man z.B. wissen will, welcher “Wochentag” z.B. der 21.03.2020 war, kann man einfach folgenden Code verwenden:

import datetime

fixe_zeit = datetime.datetime(2020, 12, 12) # datetime(year, month, day, hour, minute, second, microsecond)
fixe_zeit.weekday() 

# --- Gemäss Documentation

# 0 = Monday
# 1 = Tuesday 
# 2 = Wednesday
# 3 = Thursday
# 4 = Friday
# 5 = Saturday
# 6 = Sunday
addierte_anzahl_tage = datetime.timedelta(days = 28)
next_day = fixe_zeit + addierte_anzahl_tage
next_next_day = next_day + addierte_anzahl_tage
next_next_next_day = next_next_day + addierte_anzahl_tage
next_next_next_next_day = next_next_next_day + addierte_anzahl_tage
next_next_next_next_day

Wir sehen, dass der “21.03.2020” ein Samstag war. Ich habe es mit dem Kalender überprüft: das ist korrekt =)

Wöchentlicher gleitender Durchschnitt

### Step 1): Berechne - aus deiner stündlichen(!!) Zeitreihe - einen 1 wöchigen rolling mean:
rolling = p_train['pricesCH'].rolling(24*7, center=True).mean() # this is the rolling (weekly) mean

### Step 2): Define a function to plot different data-types (for time-series):

def plot_series(df=None, column=None, series=pd.Series([]), # the 'series'-Input will be useful, if we choose to 
                                                            # plot the series in a monthly- or weekly-frequency, 
                                                            # instead of hourly...
                label=None, ylabel=None, title=None, start=0, end=None):
    """
    Plots a certain time-series which has either been loaded in a dataframe
    and which constitutes one of its columns or it a custom pandas series 
    created by the user. The user can define either the 'df' and the 'column' 
    OR the 'series' and additionally, can also define the 'label', the 
    'ylabel', the 'title', the 'start' and the 'end' of the plot.
    """
    sns.set()
    fig, ax = plt.subplots(figsize=(30, 12))
    ax.set_xlabel('Time', fontsize=16)
    if column:
        ax.plot(df[column][start:end], label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if series.any():
        ax.plot(series, label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if label:
        ax.legend(fontsize=16)
    if title:
        ax.set_title(title, fontsize=24)
    ax.grid(True)
    return ax

### Step 3): Plot the rolling mean
ax = plot_series(p_train, 'pricesCH', label='Hourly', ylabel='Actual DA-Price (€/MWh)',
                 title='Day-Ahead Hourly Electricity Price and Weekly Rolling Mean')
ax.plot(rolling, linestyle='-', linewidth=2, label='Weekly rolling mean') # add the rolling mean to the hourly time-series
plt.show()

Saisonalität

Wöchentliche Saisonalität

### Step 1: Define a function to plot different data-types (for time-series):

def plot_series(df=None, column=None, series=pd.Series([]), # the 'series'-Input will be useful, if we choose to 
                                                            # plot the series in a monthly- or weekly-frequency, 
                                                            # instead of hourly...
                label=None, ylabel=None, title=None, start=0, end=None):
    """
    Plots a certain time-series which has either been loaded in a dataframe
    and which constitutes one of its columns or it a custom pandas series 
    created by the user. The user can define either the 'df' and the 'column' 
    OR the 'series' and additionally, can also define the 'label', the 
    'ylabel', the 'title', the 'start' and the 'end' of the plot.
    """
    sns.set()
    fig, ax = plt.subplots(figsize=(30, 12))
    ax.set_xlabel('Time', fontsize=16)
    if column:
        ax.plot(df[column][start:end], label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if series.any():
        ax.plot(series, label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if label:
        ax.legend(fontsize=16)
    if title:
        ax.set_title(title, fontsize=24)
    ax.grid(True)
    return ax

### Step 2: Plot the (hourly) Time Series over 2 weeks:
ax = plot_series(p_train, 'pricesCH', label='Hourly', ylabel='Actual Price (€/MWh)',
                 start= 1, end=1 + 24 * 14, # zoom in into 2 weeks, 750-765
                 title='Hourly Electricity Price (Zoomed - 2 Weeks)')
plt.show()

Volatilität

### Step 1): calculate the volatility (= 'change'-variable)
change = (p_train['pricesCH'].div(p_train['pricesCH'].shift(1)) - 1)*100 # Divide the 'pricesCH'-colum with the 
# lagged version of itself, substract 1, then multiply by 100, to get the growth-rate in %. Store it into a 
# new variable called 'change'.

### Step 2): Plot the percentage of the hourly change in the actual electricity price
ax = plot_series(series=change, ylabel='Hourly Change (%)', 
                 title='Percentage of the hourly change in the day-ahead electricty price')
plt.show()

Tägliche Volatilität

Here, we “Re-Sample” our hourly Volatility into daily volatility:

# Plot the electricity price (monthly frequence) along with its 1-year lagged series

# Step 1: Aggregate the data
daily_volatility = p_train['change'].asfreq('d') # here, we aggregated the hourly DA-price over days -> save it as a series

# Step 2: Define a function to plot different data-types (for time-series):

def plot_series(df=None, column=None, series=pd.Series([]), # the 'series'-Input will be useful, if we choose to plot the series in a monthly- or weekly-frequency, instead of hourly...
                label=None, ylabel=None, title=None, start=0, end=None):
    """
    Plots a certain time-series which has either been loaded in a dataframe
    and which constitutes one of its columns or it a custom pandas series 
    created by the user. The user can define either the 'df' and the 'column' 
    OR the 'series' and additionally, can also define the 'label', the 
    'ylabel', the 'title', the 'start' and the 'end' of the plot.
    """
    sns.set()
    fig, ax = plt.subplots(figsize=(30, 12))
    ax.set_xlabel('Time', fontsize=16)
    if column:
        ax.plot(df[column][start:end], label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if series.any():
        ax.plot(series, label=label)
        ax.set_ylabel(ylabel, fontsize=16)
    if label:
        ax.legend(fontsize=16)
    if title:
        ax.set_title(title, fontsize=24)
    ax.grid(True)
    return ax

# Step 3: Add the aggregated data to the plot
ax = plot_series(series=daily_volatility, ylabel='Weekly Volatility',
                 title='DAILY Volatility on the Day-Ahead Electricity Price')

# Step 4: final plot is ready
plt.show()

Dummy Variablen


Erstelle einen “normalen” Dummy

In Python benötigen man die where-Methode, um einen Dummy zu erstellen.

where in Python ist äquivalent zur ifelse()-Funktion in R:

import numpy as np

np.where(maupayment['log_month'] == maupayment['install_month'], 'install', 'existing')

“Flagg” einen Dummy nach einem Merging

Dies kann nützlich sein, wenn man nachverfolgen muss, welche der Variablen Teil des “linken” und welche Teil des “rechten” Datensatzes war (beim Merging). Dabei wird buchstäblich eine neue Spalte erstellt, welche _merge-Spalte heisst und eine Dummy-Variable darstellt.

Beispiel 1:
Erstelle einen Dummy für die “Ferien”-Spalte.

df2 = df.merge(df_year_2019, 
                 on='date', # in this case, we merge over the "date"-column; note that 'df' has 365 rows, while 'df_year_2019' only 270 rows
                 how='outer', # we need to set
                 indicator=True) # the flag is "indicator"
df2 # we get a ddset with 360 rows. On the outside, the 'testo'-ddset seems NOT to be different form the 'df', 
# however, the purpose of the merge was to create a new dummy-variable to see from which dataset it came from.
# step 2: we want some more meaningful values within the 'merge'-column --> apply an if-statement onto the (string-) column

df2.loc[df2['_merge'] == 'both', 'ferien'] = 1 # Here, we create a column 'Ferien', which will be equal to "1", if 
                                               # the value in the '_merge'-column is equal to the string "both"
df2.loc[df2['_merge'] != 'both', 'ferien'] = 'False' # Ferien-Dummy equals zero otherwise

Erstelle Dummy-Variablen für Zeitreiehen-Daten


Im Folgenden werde ich - anhand einer “Datums”-Spalte im Date-Time-Format - eine Dummy-Variable erstellen (in einer neuen Spalten), um z.B. Stunden, Viertel usw. in einer separaten Spalte als Dummy abzubilden.

Dummy für Stunden

raw_dd['Hour'] = raw_dd['Date-Time'].apply(lambda x: x.hour) # Werte von 0 [= Stunde 0:00-1:00] bis 23 [= Stunde 23:00-24:00]

Dummy für Quartale

raw_dd['Quarter'] = raw_dd['Date-Time'].apply(lambda x: x.quarter) # Werte von 1 [Januar bis März] bis 4 [Oktober bis Dezember]

Dummy für Tage

raw_dd['day_of_week'] = raw_dd['Date-Time'].apply(lambda x: x.weekday()) # Werte von 0 [= Montag] bis 6 [= Sonntag]

Anwendung einer “If”-Bedingung auf einen DataFrame


Daten-Visualisierung


Das Plotten & Abspeichern von Bildern

# Step 1: import the library matplotlib for visualization
import matplotlib.pyplot as plt # for the settings of the graph, such as title, windows-size etc...

# Step 2: make the plot
plt.figure(figsize=(14,6))
fig, ax = plot_series(y_train[10290:10320], # only take the last 30 observations from the trainings-set
            y_test, 
            y_pred, 
            labels=["y_train", "y_test", "y_pred"])
plt.xticks(rotation=90) # rotates Beschriftung der X-Achse um 90-Grad, damit man überhaupt die X-Achse lesen kann

# Step 3: save the plot
fig.savefig("monochrome-2-test.png") # save the graph

Erstelle mehrere Plots mittels For-Loops

# step 1: create a list of all the columns, you want to iterate through

cov_list = X_train.columns # I select all columns in my dataframe 'X_train'
# step 2:

for i in cov_list:
    print(i) # just as a check, to see how the loop progresses
    covariate_name = i # everytime I iterate over a new variable, I store it into this variable --> I will use this variable later to save each 
    covariate = X_train[i] # I need each variable to be of type "Series" when creating a plot
    x = list(range(0, 720, 24)) # this will put a tick-mark after each day
    x.append(719) # I also add '719', since the range()-function does not include this number
    fig, ax = plot_series(covariate[-720:]) # this plots the last month
    plt.xticks(x); # to only display some dates
    plt.xticks(rotation=90); # we need to rotate the x-axis, otherwise you cannot read it
    fig.savefig("visualisation_30_days-{}.png".format(covariate_name)) # save the graph for each column. The "trick" here is, that we can use 
    # f-strings to create a unique name for each file =)

Erstelle ein Histogram-Plot // Verteilungs-Plot

# Plot the histogram of the day-ahead electricity price

ax = p_train['pricesCH'].plot.hist(bins=18, alpha=0.65)

Python “Magic Commands”


Übersicht zu allen Spalten

Relevanz
Wenn wir versuchen, große Datensätze in Python auf dem Bildschirm anzuzeigen, wird es - zu Beginn - nicht möglich sein, alle Spalten zu sehen, weil einfach nicht genug Platz vorhanden ist, um alle Spalten anzuzeigen. Wir können jedoch einige Standardeinstellungen in pandas ändern, um ALLE Spalten sichtbar zu machen:

import pandas as pd

pd.set_option('display.max_rows', 500)

pd.set_option("display.max.columns", None) # this will tell Python: "show me ALL the columns!"
pd.set_option('display.max_columns', 500)

pd.set_option('display.width', 1000)

Mit HTML arbeiten innerhalb eines Jupyter-Notebooks

Klicke hier, um am Anfang dieses Jupyter-Notebooks zu gelangen. Dies wird erreicht durch:

  • Die Verwendungeines <a>-HTML-Tags, sowie
  • Die Benutzung einer eindeutigen ID für den <a>-Tag, UND
  • Ein (optionales) CSS-Styling auf dem <a>-Tag.
<a id="gebe-hier-passenden-id-namen" style="color:black; text-decoration: none;">Hier kommt der Text, auf welchem du zeigen willst...</a>
[And this is the text which will re-direct the user that clicks on this link](#top)

Teile Variablen zwischen Notebooks

Mit folgendem Magic Command kann man eine beliebige Variable zwischen verschiedenen Jupyter-Notebooks austauschen. Dazu muss lediglich die ursprüngliche Variable mit dem magischen Befehl %store myVariableIwannaPass übergeben. Um die Variable anschliessend im anderen Notebook abzurufen, muss man denselben Befehl %store mit dem (zusätzlichen) Parameter r übergeben.

myData = "The World Makes Sense"
%store myData

Nachdem man die Variable “myData” mit den “Magic Command” gespeichert hat (siehe obige Code-Cell), braucht man bloss ein neues Jupyter-Notebook zu öfnnen und folgenden Code einzugeben:

%store -r myData # step one: run this line in the first cell
myData # step 2: run this line in the second cell

Nützliche “Tricks”, auf denen ich gestossen bin


Reshape(-1, 1)

Was macht reshape(-1,1)?

Wenn man einen array oder eine list hat, etwa so: [1,2,3,4,5]

Würde nun Reshape(-1,1) auf [1,2,3,4,5] angewendet werden, würde eine neue Liste von Listen erstellt, mit nur einem Element in jeder Unterliste, z.B. wird die Ausgabe [[1], [2], [3], [4], [5]] sein.

Siehe zu diesem Thema auch diese Stack-Overflow Antwort.

Mathe-Tricks für Hacks


Funktion, um Zahlen stets abzurunden

Mit der round-Funktion kann man auf- ODER abrunden, wie wir es normalerweise tun:

round(47/24, ndigits = 2) # runde auf 2 Nachkommastellen

Bei der Verwendung der floor-Funktion wird das Ergebnis jedoch IMMER nach unten gerundet, wie das folgende Beispiel zeigt:

import math # um 'floo'

math.floor(47/24)

Berechne den Median

Da es keine eingebaute Funktion für den Median gibt, kann man eine Funktion schreiben, die den Median berechnen kann:

# Step 1: make a function for the case "median calculation for an ODD (= ungerade) list of numbers"

def _median_odd(xs) -> float:
    return sorted(xs)[len(xs) // 2]
# Step 2: make a function for the case "median calculation for an EVEN (= gerade) list of numbers"

def _median_even(xs) -> float:
    sorted_xs = sorted(xs)
    hi_midpoint = len(xs) // 2
    return (sorted_xs[hi_midpoint - 1] + sorted_xs[hi_midpoint]) / 2
# Step 3: Use the 2 above functions to finally be able to build the median-function

def median(v) -> float:
    return _median_even(v) if len(v) % 2 == 0 else _median_odd(v)
assert median([1, 10, 2, 9, 5]) == 5 # check if the above function was written correctly (you can change the number '5' and see
# what happens, if this expression becomes incorrect)

Fun


In diesem Abschnitt werde ich nur einige wichtige Konzepte anhand meiner “Erfindungen” darstellen:

import random 
import numpy as np

df = pd.DataFrame(np.random.randn(10, 2),
                  columns=['Col1', 'Col2'])
df['X'] = pd.Series(['A', 'A', 'A', 'A', 'A',
                     'B', 'B', 'B', 'B', 'B'])
df
for i,el in enumerate(list(df.columns.values)[:-1]):
    a = df.boxplot(el, by ='type')

Arbeitszeit Berechnung

Um zu sehen, wie viele Stunden & Minuten ich heute noch arbeiten muss, habe ich - als Übung - eine Funktion hours_to_go selbst erfasst, welche mir die Anzahl an noch zu verbleibenden Arbeitsstunden am Tag berechnet:

4*30*24
import datetime # Da wir mit "Zeit" arbeiten, brauche ich das Package "datetime"


def hours_to_go(Stunden, Minuten): # Input: wie viele Stunden & Minuten hast du heute bereits gearbeitet?
    """gibt an, wie lange ich noch heute "Schaffen" muss --> gebe dafür bloss die Anzahl stunden & minuten ein, die du "heute" 
    bereits gearbeitet hast
    > hours_to_go(3,5) --> 0507 
    interpretation: ich habe fix 8h 12min zu arbeiten. Davon ziehe ich nun 3h 5 min ab --> 0507 heisst, ich habe noch 5h 7min 
    zu arbeiten, was stimmt!"""
    
    fixe_arbeitszeit = datetime.datetime(2021, 7, 1, 8, 12) # datetime(year, month, day, hour, minute, second, microsecond), 
    # wobei die ersten 3 Argumente (Jahr, Monat, Tag) OBLIGATORISCH sind! --> hier interessiert uns die letzten 2 Inputs, 
    # nämlich die "8" und die "12" --> diese wiederspiegeln meine fixe 8h 12 min Arbeitszeit bei der SBB
    gearbeitet = datetime.timedelta(hours= Stunden, minutes= Minuten) # anzahl an Stunden, die du noch arbeiten musst
    wie_lange_noch = fixe_arbeitszeit - gearbeitet
    print(wie_lange_noch.strftime("%H%M"))
    
hours_to_go(0,5) # call the function to output, how many hours are left to work

Um zu sehen, um welche Uhrzeit ich heute Feierabend machen darf, habe ich - als Übung - eine Funktion arbeit_ende selbst erfasst:

import datetime # Da wir mit "Zeit" arbeiten, brauche ich das Package "datetime"

def arbeit_ende(Stunden, Minuten): 
    """gibt an, wann ich heute mit "Schaffen" fertig bin --> gebe dafür bloss die Anzahl stunden & minuten ein, die du "heute" 
    noch arbeiten musst, zum Beispiel arbeite ich heute noch '4h 44 min':
    > arbeit_ende(4, 44) --> 2021-07-01 17:53:02.907698 """
    
    jetzt = datetime.datetime.now() # current time
    added_time = datetime.timedelta(hours= Stunden, minutes= Minuten) # anzahl an Stunden, die du noch arbeiten musst
    ende = jetzt + added_time
    print(ende)
    
    
arbeit_ende(8,7) # call the function to output, until when I need to work today 

Zu meinen Interessen gehören Webentwicklung, Data Science, Verhaltensökonomie und alles, was mit Unternehmertum zu tun hat.