📘 Lesson 2: Introduction to Pandas#
🎯 Learning Objectives#
Gain an understanding of Pandas, including their purposes and applications.
Learn to utilize key functions within these libraries.
Engage in practical exercises to effectively apply and Pandas tools.
lesson_number = 2
print(f'lesson{lesson_number}')
lesson2
Prepare Google Colab Environment#
#@title Connect to Google Drive {display-mode:"form"}
CONNECT_TO_DRIVE = False #@param {type:"boolean"}
import os
if CONNECT_TO_DRIVE:
from google.colab import drive
# Mount Google Drive
drive.mount('/content/drive')
# Define the desired working directory path
working_dir = '/content/drive/MyDrive/hello-pypsa'
# Create the directory if it doesn't exist
if not os.path.exists(working_dir):
os.makedirs(working_dir)
print(f"Directory '{working_dir}' created.")
else:
print(f"Directory '{working_dir}' already exists.")
# Change the current working directory
os.chdir(working_dir)
print(f"Current working directory: {os.getcwd()}")
else:
print("Not connecting to Google Drive.")
import os
#@title Install Packages {display-mode:"form"}
INSTALL_PACKAGES = False #@param {type:"boolean"}
# Check if packages have already been installed in this session to prevent re-installation
if INSTALL_PACKAGES and not os.environ.get('PYPSA_PACKAGES_INSTALLED'):
!pip install pypsa pypsa[excel] folium mapclassify cartopy
!pip install git+https://github.com/PriyeshGosai/pypsa_network_viewer.git
os.environ['PYPSA_PACKAGES_INSTALLED'] = 'true'
elif not INSTALL_PACKAGES:
print("Skipping package installation.")
else:
print("PyPSA packages are already installed for this session.")
Introduction to Pandas#
What is Pandas
Pandas is a fast, powerful, flexible and easy to use open source data analysis and manipulation tool, built on top of the Python programming language (primarly on top of NumPy). It enables also to process data as one would do with SQL.
How to import Pandas
import pandas as pd
Pandas overview
A Pandas DataFrame is a two-dimensional, size-mutable and potentially heterogeneous (it means it can contain different types of data) tabular data. Dataframes can be built from dictionaries, where the dictionary keys are the column names and the dictionary values are strings that contain the column values
df_from_dict = pd.DataFrame({"Name": ["Tom", "Paul", "John", "Sarah"], "Age": [31, 42, 12, 56], "Shoe-size": [35, 42, 36, 31]})
An alternative is to use only lists
data_list = [["Tom", 31, 35], ["Paul", 42, 42], ["John", 12, 36], ["Sarah", 56, 31]]
data_column_name = ["Name", "Age", "Shoe-size"]
df_from_list = pd.DataFrame(data_list, columns=data_column_name)
Data can be fed into a Pandas DataFrame from a file. Pandas supports several file formats, which can be found at this link. For example the syntax from an excel file would be df_read_from_file = pd.read_excel(file_name, sheet_name = sheet_name).
Pandas Dataframe attributes and methods
Pandas offers built-in methods and attributes of the DataFrame class which are useful to describe the data contained in the dataframe. Given a DataFrame called dataframe_name:
dataframe_name.shape: the attribute returns a tuple representing the dimensionality (number of rows and columns) of the DataFramedataframe_name.head(n): the method displays the firstnrows of the dataframedataframe_name.tail(n): the method displays the lastnrows of the dataframedataframename.sample(n): the method returns another dataframe, containing a random sample ofnrows from the original dataframe. If no argument is passed, the method returns a dataframe with just one row. One can also executedataframe_name.sample(frac=0.1). This will return a dataframe with10 %of the rows from the original dataframedataframe_name.describe(): the method returns a 8 × ncols dataframe. Ifdataframe_namecontains numeric data, the method returns for each column,count(number of rows containing non-null values),mean,std,min,max, the25 percentile,50 percentile(the median) and75 percentiledataframe_name.isna(): the method returns a boolean same-sized dataframe, indicating if the values areNA.NAvalues, such asNoneornumpy.NaN, get mapped toTrue. Everything else gets mapped toFalse. Characters such as empty strings" "ornumpy.infare not consideredNAvalues. The snippetdataframe_name.isna().sum()returns the number of rows in each column whereNAvalues are present
df_from_list
df_from_list.shape
df_from_list.head(1)
df_from_list.tail(2)
df_from_list.sample(1)
df_from_list.describe()
df_from_list.isna()
Accessing the columns of a Pandas Dataframe
The column column_name of a DataFrame dataframe_name can be accessed as dataframe_name[”column_name”]. For example
df_from_list["Age"]
print(type(df_from_list["Age"]))
The snippet above returns a Pandas object called a Series. Where the DataFrame is (at least) a two-dimensional object, a Series is instead a one-dimensional object. One can say that a DataFrame is a container for several Series, that correspond to the DataFrame columns.
Please note: the Series contains only the data of the column. It does not contain the column name available in the DataFrame header.
Taking a slice of a Pandas DataFrame#
It is also possible to extract some columns from a DataFrame and get a DataFrame as a result. This can be achieved as
df_age_from_list = df_from_list[["Age"]]
df_age_from_list
print(type(df_age_from_list))
Adding or dropping columns in a DataFrame
The snippet below can be used to add a new column to an existing DataFrame
df_from_list["Country"]=["US", "DE", "UK", "IT"]
df_from_list
The snippet instead can be used to drop a column
df_from_list = df_from_list.drop("Country", axis=1)
df_from_list
The snippet below remains valid also to drop a row. This can be achieved with axis=0.
Further DataFrame methods
It is possible to add new columns to an existing DataFrame named dataframe_name with the following methods:
dataframe_name.map(): given a mapping dictionary, the method applies element-wise a certain mapping to aSeriesobject (as a column)
df_from_list["Country"]=["US", "DE", "UK", "IT"]
df_from_list["Country_numeric_map"] = df_from_list["Country"].map({"US": 0.0, "DE": 1.0, "UK": 2.0})
df_from_list
dataframe_name.replace: it works as themapmethod, but it has the difference that if theSeriescontains a category not mapped in the mapping dictionary, the method simply carries over the unmapped category in the new column (instead of assigning aNaNto it)
df_from_list["Country_numeric_replace"] = df_from_list["Country"].replace({"US": 0.0, "DE": 1.0, "UK": 2.0})
df_from_list
dataframe_name.apply(): the method applies a function to all values within a PandasSeriesorDataFrame. The applied function can be either built-in or customdataframe_name.applymap(): the method applies a built-in or custom function element-wise to aDataFrameobjectdataframe_name.iloc()anddataframe_name.loc(): the methods are used to slice aDataFrameby filtering on columns and/or rows.ilocperforms the slicing using indexes or index positions, whilelocusing labels or names
# iloc - filter the second row
df_from_list.iloc[[1]]
Please note that df_from_list.iloc[1] would have returned a Pandas Series instead of a Pandas DataFrame. Furthermore
# iloc - filter the second column
df_from_list.iloc[:, 1]
# iloc - filter just the second, the fourth and the fifth columns
df_from_list.iloc[:, [1,3,4]]
# loc - filter the second row
df_from_list.loc[[1]]
Please note that df_from_list.loc[1] would have returned a Pandas Series instead of a Pandas DataFrame. Furthermore
# loc - filter the second column
df_from_list.loc[:, "Age"]
# loc - filter just the second, the fourth and the fifth columns
df_from_list.loc[:, ["Age", "Country", "Country_numeric_map"]]
dataframe_name.rename(): the method renames a column, by means of a mapping dictionary
df_from_list.rename(columns={"Age": "Age_renamed"})
Merging and aggregating DataFrame
The merge function in Pandas performs on DataFrame the equivalent operations of an SQL join. Given two dataframes, the merge happens along shared column/s that exist in both of them. The basic syntax is pd.merge(df_a ,df_b ,how=”type_of_join”,on=”col_name”). The full documentation is available at this link.
Pandas allows to perform groupby operations. They involve grouping by a column, applying an aggregating function and combining the results. Given the following DataFrame
data_dictionary = {
'Name': ['John', 'Paul', 'Tom', 'Bob', 'Ronan',
'Kirby', 'Sarah', 'Joe', 'Donald', 'Jeffrey'],
'Department': ['Administration', 'Marketing', 'Technical', 'Technical', 'Marketing',
'Administration', 'Technical', 'Marketing', 'Technical', 'Administration'],
'Employment Type': ['Full-time', 'Intern', 'Intern', 'Part-time', 'Part-time',
'Full-time', 'Full-time', 'Intern', 'Intern', 'Full-time'],
'Salary': [12, 50, 700, 700, 550,
12, 1250, 600, 111, 12]}
# Create the DataFrame
df = pd.DataFrame(data_dictionary)
df
the snippet below groups column Salary by column Department and applies a sum
df.groupby("Department")["Salary"].sum()
or the snippet below groups column Department by column Employment Type and counts the occurences
df.groupby("Employment Type")["Department"].count()
Data from Excel spreadsheets or CSV files can be imported into Python using Pandas
df_power_plant = pd.read_csv("data/powerplants.csv",index_col=0)
df_power_plant
Exercise: Power Plant Data Manipulation#
Task 1: View the column names
Use the columns attribute to view the names of all the columns in the dataset
# Provide code here
Task 2: Find Unique Options for ‘Fueltype’, ‘Technology’, and ‘Country’
Use the
unique()function to find and print the unique values in the following columns:FueltypeTechnologyCountry
Task 3: Filter Data by Technology
Assume you want to filter the data for a technology called
'Solar'.Write the code to filter the DataFrame and show all power plants using
'Solar'technology.
# Provide code here
Task 4: Count the Number of Power Plants with a Certain Fuel
Assume the fuel type is
'Coal'.Write the code to count and print the number of power plants that use
'Coal'as the fuel type.
# Provide code here
Task 5: Calculate the Capacity of All Generators in a Country
Assume you want to calculate the total capacity of all power plants in
'South Africa'.Write the code to sum and print the total capacity of the power plants located in
'South Africa'.
# Provide code here
Task 6: Change the Capacity of One Power Plant
Assume you want to change the capacity of a power plant named
'Plant A'to500 MW.Write the code to update the capacity of
'Plant A'to500 MW.
# Provide code here
Task 7: Add a New Power Plant
Add a new power plant to the DataFrame from the dictionary
new_plant
new_plant = {
'Name': 'New Plant',
'Fueltype': 'Wind',
'Technology': 'Wind Turbine',
'Set': 'Set1',
'Country': 'Germany',
'Capacity': 100,
'Efficiency': 0.35,
'Duration': 30,
'Volume_Mm3': 0,
'DamHeight_m': 0,
'StorageCapacity_MWh': 0,
'DateIn': '2025-01-01',
'DateRetrofit': None,
'DateOut': None,
'lat': 51.1657,
'lon': 10.4515,
'EIC': 'EIC001',
'projectID': 'proj001',
'bus': 'bus001'
}
# Provide code here
Additional Resources#
Answers#
# 1. View the column names
print("Column names:")
print(df_power_plant.columns)
# 2. Find the unique options for 'Fueltype', 'Technology', 'Country'
print("\nUnique Fueltypes:")
print(df_power_plant['Fueltype'].unique())
print("\nUnique Technologies:")
print(df_power_plant['Technology'].unique())
print("\nUnique Countries:")
print(df_power_plant['Country'].unique())
# 3. Basic code to filter the data by technology (assume a technology name)
technology_name = 'Solar' # Example technology name
filtered_df = df_power_plant[df_power_plant['Technology'] == technology_name]
print(f"\nPower plants with {technology_name} technology:")
print(filtered_df)
# 4. Count the number of power plants with a certain fuel
fuel_type = 'Coal' # Example fuel type
fuel_count = df_power_plant[df_power_plant['Fueltype'] == fuel_type].shape[0]
print(f"\nNumber of power plants using {fuel_type}: {fuel_count}")
# 5. Calculate the capacity of all generators in a country
country_name = 'South Africa' # Example country
total_capacity = df_power_plant[df_power_plant['Country'] == country_name]['Capacity'].sum()
print(f"\nTotal capacity of power plants in {country_name}: {total_capacity} MW")
# 6. Change the capacity of one of the power plants
# Let's assume we want to change the capacity of a specific power plant (identified by Name)
plant_name = 'Plant A' # Example power plant name
new_capacity = 500 # New capacity in MW
df_power_plant.loc[df_power_plant['Name'] == plant_name, 'Capacity'] = new_capacity
print(f"\nUpdated capacity of {plant_name}: {new_capacity} MW")
# Task 7: Add a new power plant
new_plant = {
'Name': 'New Plant',
'Fueltype': 'Wind',
'Technology': 'Wind Turbine',
'Set': 'Set1',
'Country': 'Germany',
'Capacity': 100,
'Efficiency': 0.35,
'Duration': 30,
'Volume_Mm3': 0,
'DamHeight_m': 0,
'StorageCapacity_MWh': 0,
'DateIn': '2025-01-01',
'DateRetrofit': None,
'DateOut': None,
'lat': 51.1657,
'lon': 10.4515,
'EIC': 'EIC001',
'projectID': 'proj001',
'bus': 'bus001'
}
# Convert the new plant dictionary to a DataFrame
new_plant_df = pd.DataFrame([new_plant])
# Concatenate the new plant to the existing DataFrame
df_power_plant = pd.concat([df_power_plant, new_plant_df], ignore_index=True)
# Print the last added row to confirm
print("\nNew power plant added:")
print(df_power_plant.tail(1)) # Display the last added row