Calculating Monthly Anomalies in Pandas

Viewed 980

Hello StackOverflow Community,

I have been interested in calculating anomalies for data in pandas 1.2.0 using Python 3.9.1 and Numpy 1.19.5, but have been struggling to figure out the most "Pythonic" and "pandas" way to complete this task (or any way for that matter). Below I have some created some dummy data and put it into a pandas DataFrame. In addition, I have tried to clearly outline my methodology for calculating monthly anomalies for the dummy data.

What I am trying to do is take "n" years of monthly values (in this example, 2 years of monthly data = 25 months) and calculate monthly averages for all years (for example group all the January values togeather and calculate the mean). I have been able to do this using pandas.

Next, I would like to take each monthly average and subtract it from all elements in my DataFrame that fall into that specific month (for example subtract each January value from the overall January mean value). In the code below you will see some lines of code that attempt to do this subtraction, but to no avail.

If anyone has any thought or tips on what may be a good way to approach this, I really appreciate your insight. If you require further clarification, let me know. Thanks for your time and thoughts.

-Marian

#Import packages
import numpy as np
import pandas as pd
#-------------------------------------------------------------
#Create a pandas dataframe with some data that will represent:
#Column of dates for two years, at monthly resolution
#Column of corresponding values for each date.

#Create two years worth of monthly dates
dates = pd.date_range(start='2018-01-01', end='2020-01-01', freq='MS')

#Create some random data that will act as our data that we want to compute the anomalies of
values = np.random.randint(0,100,size=25)

#Put our dates and values into a dataframe to demonsrate how we have tried to calculate our anomalies
df = pd.DataFrame({'Dates': dates, 'Values': values})
#-------------------------------------------------------------
#Anomalies will be computed by finding the mean value of each month over all years
#And then subtracting the mean value of each month by each element that is in that particular month

#Group our df according to the month of each entry and calculate monthly mean for each month
monthly_means = df.groupby(df['Dates'].dt.month).mean()
#-------------------------------------------------------------
#Now, how do we go about subtracting these grouped monthly means from each element that falls
#in the corresponding month. 
#For example, if the monthly mean over 2 years for January is 20 and the value is 21 in January 2018, the anomaly would be +1 for January 2018

#Example lines of code I have tried, but have not worked

#ValueError:Unable to coerce to Series, length must be 1: given 12
#anomalies = socal_csv.groupby(socal_csv['Date'].dt.month) - monthly_means

#TypeError: unhashable type: "list"
#anomalies = socal_csv.groupby(socal_csv['Date'].dt.month).transform([np.subtract])
2 Answers

You can use pd.merge like this :

import numpy as np
import pandas as pd

dates = pd.date_range(start='2018-01-01', end='2020-01-01', freq='MS')


values = np.random.randint(0,100,size=25)


df = pd.DataFrame({'Dates': dates, 'Values': values})

monthly_means = df.groupby(df['Dates'].dt.month.mean()


df['month']=df['Dates'].dt.strftime("%m").astype(int)
df=df.merge(monthly_means.rename(columns={'Dates':'month','Values':'Mean'}),on='month',how='left')
df['Diff']=df['Mean']-df['Values']

output:

 df['Diff']
Out[19]: 
0     33.333333
1     19.500000
2    -29.500000
3    -22.500000
4    -24.000000
5     -3.000000
6     10.000000
7      2.500000
8     14.500000
9    -17.500000
10    44.000000
11    31.000000
12   -11.666667
13   -19.500000
14    29.500000
15    22.500000
16    24.000000
17     3.000000
18   -10.000000
19    -2.500000
20   -14.500000
21    17.500000
22   -44.000000
23   -31.000000
24   -21.666667

You can use abs() if you want absolute difference

A one-line solution is:

df = pd.DataFrame({'Values': values}, index=dates)
df.groupby(df.index.month).transform(lambda x: x-x.mean())
Related