1.8. Python: Descriptives and Indexing#
In this notebook we cover some key Pandas syntax and functions:
Syntax for getting various descriptive statistics from a
Pandas
dataframeSyntax for indexing a dataframe - finding the rows and columns that you need
this allows you to get descriptives for specific rows and columns
Here we meet indexing in the context of descriptive statistics, but indexing is something you will do every single time you write code for data analysis. Incorrect syntax in indexing is the number one biggest source of bugs for student on this course, so it is well worth spending the time to get to grips with it.
You absolutely should work through all the exercises in this notebook in advance of the tutorial.
1.8.1. Set up Python Libraries#
As usual you will need to run this code block to import the relevant Python libraries
# Set-up Python libraries - you need to run this but you don't need to change it
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
import pandas as pd
import seaborn as sns
sns.set_theme(style='white')
import statsmodels.api as sm
import statsmodels.formula.api as smf
1.8.2. Import a dataset to work with#
We will work with weather data from the Oxford weather station. This code block will read it automatically from the internet.
weather = pd.read_csv("https://raw.githubusercontent.com/jillxoreilly/StatsCourseBook_2024/main/data/OxfordWeather.csv")
display(weather)
YYYY | Month | MM | DD | DD365 | Tmax | Tmin | Tmean | Trange | Rainfall_mm | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1827 | Jan | 1 | 1 | 1 | 8.3 | 5.6 | 7.0 | 2.7 | 0.0 |
1 | 1827 | Jan | 1 | 2 | 2 | 2.2 | 0.0 | 1.1 | 2.2 | 0.0 |
2 | 1827 | Jan | 1 | 3 | 3 | -2.2 | -8.3 | -5.3 | 6.1 | 9.7 |
3 | 1827 | Jan | 1 | 4 | 4 | -1.7 | -7.8 | -4.8 | 6.1 | 0.0 |
4 | 1827 | Jan | 1 | 5 | 5 | 0.0 | -10.6 | -5.3 | 10.6 | 0.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
71338 | 2022 | Apr | 4 | 26 | 116 | 15.2 | 4.1 | 9.7 | 11.1 | 0.0 |
71339 | 2022 | Apr | 4 | 27 | 117 | 10.7 | 2.6 | 6.7 | 8.1 | 0.0 |
71340 | 2022 | Apr | 4 | 28 | 118 | 12.7 | 3.9 | 8.3 | 8.8 | 0.0 |
71341 | 2022 | Apr | 4 | 29 | 119 | 11.7 | 6.7 | 9.2 | 5.0 | 0.0 |
71342 | 2022 | Apr | 4 | 30 | 120 | 17.6 | 1.0 | 9.3 | 16.6 | 0.0 |
71343 rows × 10 columns
1.8.3. Get descriptive statistics#
We will look at indexing in the context of getting descriptive statistics for the different variables in a dataframe, and/or for a selection of rows.
Pandas has a number of built-in functions to get descriptive statistics:
df.mean()
- gets the meandf.median()
- gets the mediandf.var()
- gets the variancedf.std()
- gets the standard deviationdf.min()
- gets the minimum valuedf.max()
- gets the maximum valuedf.corr()
- gets the correlation coefficient (Pearson or Spearman)
in the function names above, df
is just a placeholder for “dataframe”
You can replace df
with the name of your dataframe (and we need to tell the function to use only columns containing numerical data)
weather.mean(numeric_only=True)
YYYY 1924.165174
MM 6.516210
DD 15.729154
DD365 182.917077
Tmax 13.890153
Tmin 6.156018
Tmean 10.046208
Trange 7.734244
Rainfall_mm 1.786964
dtype: float64
Which columns from the dataframe are missing from the table of means?
Month is missing because the values are strings (words); we only get the mean for numerical columns
1.8.4. Decriptives for a single column#
Format:
df.column.mean()
You might just want the mean for one column, in which case you can include the column name as follows:
weather.Tmax.mean()
13.890153065515406
Instead of a whole table of means, many of which were irrelevant, we now just have the single number we wanted - the mean of Tmax
Think check that the value obtained this way matches the one in the table above.
Why does it work?#
The commmand weather.Tmax.mean()
has two steps.
Step 1:
The syntax weather.Tmax
(or more generally, df.column
) returns just the one column of the dataframe as a series (essentially a one-column table).
Look:
weather.Tmax
0 8.3
1 2.2
2 -2.2
3 -1.7
4 0.0
...
71338 15.2
71339 10.7
71340 12.7
71341 11.7
71342 17.6
Name: Tmax, Length: 71343, dtype: float64
If we liked, we could give this single-column series a name a ‘save’ it:
DailyMaxima = weather.Tmax
print(DailyMaxima)
0 8.3
1 2.2
2 -2.2
3 -1.7
4 0.0
...
71338 15.2
71339 10.7
71340 12.7
71341 11.7
71342 17.6
Name: Tmax, Length: 71343, dtype: float64
However, we don’t need to do that as we can just tack the .mean()
function onto it directly as follows:
Step 2:
The syntax .mean()
gets the mean of the thing before the dot (i.e. the series you created with the command weather.Tmax
)
weather.Tmax.mean()
13.890153065515406
Exercises#
Complete the following quick practice questions:
# get the mean daily rainfall
# get the median daily rainfall
# get the standard deviation of the maximum daily temperature
# find the minimum value of "year" (you will need two separate commands)
1.8.5. Get descriptives for a subset of rows#
df.query('columnname == number')
df.query('columnname == "string"')
df.query('columnname1 == value1 and columnname2 == value2')
Say I want to know the mean daily maximum temperatures for the year 1921.
Step 1: find the relevant rows#
I first need to somehow pull out the rows of the table where the value for YYYY is 1921
I can do this using the function df.query
, for example weather.query(‘YYYY == 1921’)
note the quote marks surrounding the whole query
note the double equals sign
==
, which is used for checking if two bvalues are equal (as opposed to setting a value)
# get all the rows where year is 1921
weather.loc[weather.YYYY == 1921]
YYYY | Month | MM | DD | DD365 | Tmax | Tmin | Tmean | Trange | Rainfall_mm | |
---|---|---|---|---|---|---|---|---|---|---|
34333 | 1921 | Dec | 12 | 31 | 1 | 9.1 | 3.3 | 6.2 | 5.8 | 0.0 |
34334 | 1921 | Jan | 1 | 1 | 2 | 12.2 | 7.1 | 9.7 | 5.1 | 9.7 |
34335 | 1921 | Jan | 1 | 2 | 3 | 11.9 | 8.7 | 10.3 | 3.2 | 6.1 |
34336 | 1921 | Jan | 1 | 3 | 4 | 9.9 | 5.9 | 7.9 | 4.0 | 0.0 |
34337 | 1921 | Jan | 1 | 4 | 5 | 12.3 | 9.8 | 11.1 | 2.5 | 5.5 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
34693 | 1921 | Dec | 12 | 26 | 361 | 10.8 | -3.1 | 3.9 | 13.9 | 5.6 |
34694 | 1921 | Dec | 12 | 27 | 362 | 11.2 | 5.4 | 8.3 | 5.8 | 1.3 |
34695 | 1921 | Dec | 12 | 28 | 363 | 13.9 | 3.9 | 8.9 | 10.0 | 0.0 |
34696 | 1921 | Dec | 12 | 29 | 364 | 7.6 | 1.7 | 4.7 | 5.9 | 0.8 |
34697 | 1921 | Dec | 12 | 30 | 365 | 11.7 | 1.9 | 6.8 | 9.8 | 4.5 |
365 rows × 10 columns
Note the size of this table - there are 365 rows. Why?
In the original table
weather
, there is one row per day365 of those rows (days) match the criterion ‘YYYY = 1921’ because there are 365 days in a year.
If I wanted to, I could give this table a name and ‘save’ it for later use:
weather1921 = weather.query('YYYY == 1921') # create a new dataframe for just 1921
weather1921 # look at my new dataframe
YYYY | Month | MM | DD | DD365 | Tmax | Tmin | Tmean | Trange | Rainfall_mm | |
---|---|---|---|---|---|---|---|---|---|---|
34333 | 1921 | Dec | 12 | 31 | 1 | 9.1 | 3.3 | 6.2 | 5.8 | 0.0 |
34334 | 1921 | Jan | 1 | 1 | 2 | 12.2 | 7.1 | 9.7 | 5.1 | 9.7 |
34335 | 1921 | Jan | 1 | 2 | 3 | 11.9 | 8.7 | 10.3 | 3.2 | 6.1 |
34336 | 1921 | Jan | 1 | 3 | 4 | 9.9 | 5.9 | 7.9 | 4.0 | 0.0 |
34337 | 1921 | Jan | 1 | 4 | 5 | 12.3 | 9.8 | 11.1 | 2.5 | 5.5 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
34693 | 1921 | Dec | 12 | 26 | 361 | 10.8 | -3.1 | 3.9 | 13.9 | 5.6 |
34694 | 1921 | Dec | 12 | 27 | 362 | 11.2 | 5.4 | 8.3 | 5.8 | 1.3 |
34695 | 1921 | Dec | 12 | 28 | 363 | 13.9 | 3.9 | 8.9 | 10.0 | 0.0 |
34696 | 1921 | Dec | 12 | 29 | 364 | 7.6 | 1.7 | 4.7 | 5.9 | 0.8 |
34697 | 1921 | Dec | 12 | 30 | 365 | 11.7 | 1.9 | 6.8 | 9.8 | 4.5 |
365 rows × 10 columns
… but I don’t need to do this as I can tack on more commands to complete my goal in a single line of code.
Step 2: find the relevant column#
Now that I have grabbed the relevant rows, I narrow it down to column Tmax
weather.query('YYYY == 1920').Tmax
33967 3.2
33968 13.2
33969 4.5
33970 3.7
33971 3.7
...
34328 12.1
34329 11.9
34330 12.3
34331 12.4
34332 12.1
Name: Tmax, Length: 366, dtype: float64
Step 4: add in the actual function#
FInally, I can tack on teh function I want to run on my selected rows and column: mean()
:
weather.query('YYYY == 1920').Tmax.mean()
13.701092896174861
Exercises#
Complete the following quick practice questions:
# Get the mean daily maximum temperature in 2006
# Get the mean daily minimum temperature in 1947
1.8.6. Get descriptives for one category#
What about getting the mean daily maximum temperatures for, say, October? So I need to pull out all the rows in which ’MM’ matches ’Oct’
The syntax is very similar, but now I am matching a string "Oct"
, so it needs to be in quotes:
weather.query('Month == "Oct"').Tmax.mean()
14.112638544251446
note you can actually use single and double quotes interchangeably in Python, but in cases like this where you have quotes within quotes, it is better to use both types to avoid the computer getting confused about which opening and closing quotes to pair up.
so you could do either of these
weather.query(‘MM == “Oct”’).Tmax.mean()
weather.query(“MM == ‘Oct’”).Tmax.mean()
but not these
weather.query(‘MM == ‘Oct’’).Tmax.mean()
weather.query(“MM == “Oct””).Tmax.mean()
Exercises#
Complete the following quick practice questions:
# Get the mean daily rainfall in January
# Get the mean daily rainfall in June
1.8.7. Match multiple conditions#
What if I want to know the mean daily maximum temperature in 1920 for June only?
We can simply use and
or &
to pass df.query()
multiple conditions:
# mean daily maximum temperature in 1920
weather.query('YYYY == 1920').Tmax.mean()
13.701092896174861
# mean daily maximumm temperature in 1920
weather.query('YYYY == 1920 & MM == 6').Tmax.mean()
19.553333333333335
Think hopefully the value for June is higher than the average for the whole year? If not something has gone wrong!
Exercises#
Complete the following quick practice questions:
# Get the mean daily rainfall in June 2007
# Get the minimum temperature in January 1947
1.8.8. Syntax for correlation#
When we run a correlation, we need to index two columns - the two we are correlating.
Using the pandas
function df.corr()
we can do so as follows:
# get the correlation between columns Tmin and Tmax of dataframe weather
weather.Tmin.corr(weather.Tmax)
0.8414800914062084
We can set the type of correlation to Pearson or Spearman as appropriate (the default is Pearson’s \(r\)):
# get the correlation between columns Tmin and Tmax of dataframe weather
weather.Tmin.corr(weather.Tmax, method='spearman')
0.8438796074978867
Exercises#
Complete the following practice questions to help you get to grips with the syntax for correlation:
# Find the Spearman correlation between daily mean temperature Tmean, and rainfall
weather.Tmean.corr(weather.Rainfall_mm, method='spearman')
-0.019287092574398337
# Find the correlation between year and daily mean temperature
weather.Tmean.corr(weather.YYYY)
0.08304423160159409
All-by-all correlation matrix#
We can also quickly output all the correlations between all possible pairs of columns in a dataframe by simply not giving any column index (but we do have to tell it to only use the numeric columns):
weather.corr(numeric_only=True)
YYYY | MM | DD | DD365 | Tmax | Tmin | Tmean | Trange | Rainfall_mm | |
---|---|---|---|---|---|---|---|---|---|
YYYY | 1.000000 | -0.003411 | -0.000059 | -0.003372 | 0.071631 | 0.089683 | 0.083044 | -0.001257 | 0.008117 |
MM | -0.003411 | 1.000000 | 0.010567 | 0.995580 | 0.179681 | 0.235401 | 0.213082 | -0.018820 | 0.043672 |
DD | -0.000059 | 0.010567 | 1.000000 | 0.092771 | 0.001217 | 0.002876 | 0.002035 | -0.002055 | 0.005315 |
DD365 | -0.003372 | 0.995580 | 0.092771 | 1.000000 | 0.177016 | 0.233248 | 0.210545 | -0.020536 | 0.043925 |
Tmax | 0.071631 | 0.179681 | 0.001217 | 0.177016 | 1.000000 | 0.841480 | 0.967881 | 0.593339 | -0.008807 |
Tmin | 0.089683 | 0.235401 | 0.002876 | 0.233248 | 0.841480 | 1.000000 | 0.950248 | 0.064379 | 0.086181 |
Tmean | 0.083044 | 0.213082 | 0.002035 | 0.210545 | 0.967881 | 0.950248 | 1.000000 | 0.371965 | 0.035037 |
Trange | -0.001257 | -0.018820 | -0.002055 | -0.020536 | 0.593339 | 0.064379 | 0.371965 | 1.000000 | -0.144654 |
Rainfall_mm | 0.008117 | 0.043672 | 0.005315 | 0.043925 | -0.008807 | 0.086181 | 0.035037 | -0.144654 | 1.000000 |
The problem with this is that the figure we want is often buried amongst a lot of irrelevant correlation coefficients.
Correlation using scipy.stats
#
Note that here we aare using the pandas
function df.corr()
Later in the course we will use two functions from a library called scipy.stats
:
stats.pearsonr()
stats.spearmanr()
These are actually a bit more useful as they give us a \(p\)-value for the correlation as well as the correlation coefficient
don’t worry if you don’t know what a \(p\)-value is yet - this will be covered later in the course
scipy.stats
will be used extensively in the module on hypothesis testing