rolling moving average and std dev by multiple columns dynamically - python

I have a dataframe like this
import pandas as pd
import numpy as np
raw_data = {'Country':['UK','UK','UK','UK','UK','UK','UK','UK','UK','UK','UK','UK','UK','UK','UK','US','US','US','US','US','US'],
'Product':['A','A','A','A','B','B','B','B','B','B','B','B','C','C','C','D','D','D','D','D','D'],
'Week': [1,2,3,4,1,2,3,4,5,6,7,8,1,2,3,1,2,3,4,5,6],
'val': [5,4,3,1,5,6,7,8,9,10,11,12,5,5,5,5,6,7,8,9,10]
}
df2 = pd.DataFrame(raw_data, columns = ['Country','Product','Week', 'val'])
print(df2)
i want to calculate moving average and std dev for val column by country and product..like 3 weeks,5 weeks ,7 weeks etc
wanted dataframe:
'Contry', 'product','week',val', '3wks_avg' '3wks_std','5wks_avg',5wks,std'..etc

Like WenYoBen suggested, we can create a list of all the window sizes you want, and then dynamically create your wanted columns with GroupBy.rolling:
weeks = [3, 5, 7]
for week in weeks:
df[[f'{week}wks_avg', f'{week}wks_std']] = (
df.groupby(['Country', 'Product']).rolling(window=week, on='Week')['val']
.agg(['mean', 'std']).reset_index(drop=True)
)
Country Product Week val 3wks_avg 3wks_std 5wks_avg 5wks_std 7wks_avg 7wks_std
0 UK A 1 5 nan nan nan nan nan nan
1 UK A 2 4 nan nan nan nan nan nan
2 UK A 3 3 4.00 1.00 nan nan nan nan
3 UK A 4 1 2.67 1.53 nan nan nan nan
4 UK B 1 5 nan nan nan nan nan nan
5 UK B 2 6 nan nan nan nan nan nan
6 UK B 3 7 6.00 1.00 nan nan nan nan
7 UK B 4 8 7.00 1.00 nan nan nan nan
8 UK B 5 9 8.00 1.00 7.00 1.58 nan nan
9 UK B 6 10 9.00 1.00 8.00 1.58 nan nan
10 UK B 7 11 10.00 1.00 9.00 1.58 8.00 2.16
11 UK B 8 12 11.00 1.00 10.00 1.58 9.00 2.16
12 UK C 1 5 nan nan nan nan nan nan
13 UK C 2 5 nan nan nan nan nan nan
14 UK C 3 5 5.00 0.00 nan nan nan nan
15 US D 1 5 nan nan nan nan nan nan
16 US D 2 6 nan nan nan nan nan nan
17 US D 3 7 6.00 1.00 nan nan nan nan
18 US D 4 8 7.00 1.00 nan nan nan nan
19 US D 5 9 8.00 1.00 7.00 1.58 nan nan
20 US D 6 10 9.00 1.00 8.00 1.58 nan nan

This is how you would get the moving average for 3 weeks :
df['3weeks_avg'] = list(df.groupby(['Country', 'Product']).rolling(3).mean()['val'])
Apply the same principle for the other columns you want to compute.

IIUC, you may try this
wks = ['Week_3', 'Week_5', 'Week_7']
df_calc = (df2.groupby(['Country', 'Product']).expanding().val
.agg(['mean', 'std']).rename(lambda x: f'Week_{x+1}', level=-1)
.query('ilevel_2 in #wks').unstack())
Out[246]:
mean std
Week_3 Week_5 Week_7 Week_3 Week_5 Week_7
Country Product
UK A 4.0 NaN NaN 1.0 NaN NaN
B NaN 5.0 6.0 NaN NaN 1.0

You will want to use a groupby-transform to get the rolling moments of your data. The following should compute what you are looking for:
weeks = [3, 5, 7] # define weeks
df2 = df2.sort_values('Week') # order by time
for i in weeks: # loop through time intervals you want to compute
df2['{}wks_avg'.format(i)] = df2.groupby(['Country', 'Product'])['val'].transform(lambda x: x.rolling(i).mean()) # i-week rolling mean
df2['{}wks_std'.format(i)] = df2.groupby(['Country', 'Product'])['val'].transform(lambda x: x.rolling(i).std()) # i-week rolling std
Here is what the resulting dataframe will look like.
print(df2.dropna().head().to_string())
Country Product Week val 3wks_avg 3wks_std 5wks_avg 5wks_std 7wks_avg 7wks_std
17 US D 3 7 6.0 1.0 6.0 1.0 6.0 1.0
6 UK B 3 7 6.0 1.0 6.0 1.0 6.0 1.0
14 UK C 3 5 5.0 0.0 5.0 0.0 5.0 0.0
2 UK A 3 3 4.0 1.0 4.0 1.0 4.0 1.0
7 UK B 4 8 7.0 1.0 7.0 1.0 7.0 1.0

Related

Convert two pandas rows into one

I want to convert below dataframe,
ID TYPE A B
0 1 MISSING 0.0 0.0
1 2 1T 1.0 2.0
2 2 2T 3.0 4.0
3 3 MISSING 0.0 0.0
4 4 2T 10.0 4.0
5 5 CBN 15.0 20.0
6 5 DSV 25.0 35.0
to:
ID MISSING_A MISSING_B 1T_A 1T_B 2T_A 2T_B CBN_A CBN_B DSV_A DSV_B
0 1 0.0 0.0 NaN NaN NaN NaN NaN NaN NaN NaN
1 2 NaN NaN 1.0 2.0 3.0 4.0 NaN NaN NaN NaN
3 3 0.0 0.0 NaN NaN NaN NaN NaN NaN NaN NaN
4 4 10.0 4.0 NaN NaN 10.0 4.0 NaN NaN NaN NaN
5 5 NaN NaN NaN NaN NaN NaN 15.0 20.0 25.0 35.0
For IDs with multiple types, multiple rows for A and B to merge into one row as shown above.
You are looking for a pivot, which will end up giving you a multi-index. You'll need to join those columns to get the suffix you are looking for.
df = df.pivot(index='ID',columns='TYPE', values=['A','B'])
df.columns = ['_'.join(reversed(col)).strip() for col in df.columns.values]
df.reset_index()

Pandas: if Column A is blank, Column B else Column A

Current_df:
Unnamed: 0 Div Date Time HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR Referee Unnamed: 62 GB>2.5 GB<2.5 GBAHH GBAHA GBAH HT AT
0 0 E0 2019-08-09 20:00:00 Liverpool Norwich 4 1 H 4 0 H M Oliver NaN NaN NaN NaN NaN NaN NaN NaN
1 1 E0 2019-08-10 12:30:00 West Ham Man City 0 5 A 0 1 A M Dean NaN NaN NaN NaN NaN NaN NaN NaN
2 2 E0 2019-08-10 15:00:00 Bournemouth Sheffield United 1 1 D 0 0 D K Friend NaN NaN NaN NaN NaN NaN NaN NaN
3 3 E0 2019-08-10 15:00:00 Burnley Southampton 3 0 H 0 0 D G Scott NaN NaN NaN NaN NaN NaN NaN NaN
4 4 E0 2019-08-10 15:00:00 Crystal Palace Everton 0 0 D 0 0 D J Moss NaN NaN NaN NaN NaN NaN NaN NaN
5 5 E0 2019-08-10 15:00:00 Watford Brighton 0 3 A 0 1 A C Pawson NaN NaN NaN NaN NaN NaN NaN NaN
6 6 E0 2019-08-10 17:30:00 Tottenham Aston Villa 3 1 H 0 1 A C Kavanagh NaN NaN NaN NaN NaN NaN NaN NaN
7 7 E0 2019-08-11 14:00:00 Leicester Wolves 0 0 D 0 0 D A Marriner NaN NaN NaN NaN NaN NaN NaN NaN
8 7084 G1 2004-09-18 NaN NaN NaN 0 1 A 0 0 D NaN NaN 1.83 1.83 1.66 1.95 0.5 Ergotelis Iraklis
9 7085 G1 2004-09-18 NaN NaN NaN 3 1 H 1 1 D NaN NaN 2.00 1.65 1.90 1.71 -0.5 Xanthi Aris
10 7086 G1 2004-09-19 NaN NaN NaN 1 0 H 1 0 H NaN NaN 2.00 1.65 1.85 1.85 0.0 Chalkidona Panionios
11 7087 G1 2004-09-19 NaN NaN NaN 1 1 D 0 0 D NaN NaN 1.83 1.83 1.67 1.95 0.5 Egaleo AEK
12 7088 G1 2004-09-19 NaN NaN NaN 1 0 H 1 0 H NaN NaN 1.85 1.79 1.85 1.85 0.0 Kalamaria OFI
13 7089 G1 2004-09-19 NaN NaN NaN 2 1 H 1 1 D NaN NaN NaN NaN NaN NaN NaN Olympiakos Kalithea
14 7090 G1 2004-09-19 NaN NaN NaN 3 0 H 2 0 H NaN NaN NaN NaN NaN NaN NaN Panathinaikos Ionikos
Expected df:
Unnamed: 0 Div Date Time HomeTeam AwayTeam FTHG FTAG FTR HTHG HTAG HTR Referee Unnamed: 62 GB>2.5 GB<2.5 GBAHH GBAHA GBAH HT AT
0 0 E0 2019-08-09 20:00:00 Liverpool Norwich 4 1 H 4 0 H M Oliver NaN NaN NaN NaN NaN NaN NaN NaN
1 1 E0 2019-08-10 12:30:00 West Ham Man City 0 5 A 0 1 A M Dean NaN NaN NaN NaN NaN NaN NaN NaN
2 2 E0 2019-08-10 15:00:00 Bournemouth Sheffield United 1 1 D 0 0 D K Friend NaN NaN NaN NaN NaN NaN NaN NaN
3 3 E0 2019-08-10 15:00:00 Burnley Southampton 3 0 H 0 0 D G Scott NaN NaN NaN NaN NaN NaN NaN NaN
4 4 E0 2019-08-10 15:00:00 Crystal Palace Everton 0 0 D 0 0 D J Moss NaN NaN NaN NaN NaN NaN NaN NaN
5 5 E0 2019-08-10 15:00:00 Watford Brighton 0 3 A 0 1 A C Pawson NaN NaN NaN NaN NaN NaN NaN NaN
6 6 E0 2019-08-10 17:30:00 Tottenham Aston Villa 3 1 H 0 1 A C Kavanagh NaN NaN NaN NaN NaN NaN NaN NaN
7 7 E0 2019-08-11 14:00:00 Leicester Wolves 0 0 D 0 0 D A Marriner NaN NaN NaN NaN NaN NaN NaN NaN
8 7084 G1 2004-09-18 NaN NaN NaN 0 1 A 0 0 D NaN NaN 1.83 1.83 1.66 1.95 0.5 NaN NaN
9 7085 G1 2004-09-18 NaN Ergotelis Iraklis 3 1 H 1 1 D NaN NaN 2.00 1.65 1.90 1.71 -0.5 NaN NaN
10 7086 G1 2004-09-19 NaN Xanthi Aris 1 0 H 1 0 H NaN NaN 2.00 1.65 1.85 1.85 0.0 NaN NaN
11 7087 G1 2004-09-19 NaN Chalkidona Panionios 1 1 D 0 0 D NaN NaN 1.83 1.83 1.67 1.95 0.5 NaN NaN
12 7088 G1 2004-09-19 NaN Egaleo AEK 1 0 H 1 0 H NaN NaN 1.85 1.79 1.85 1.85 0.0 NaN NaN
13 7089 G1 2004-09-19 NaN Kalamaria OFI 2 1 H 1 1 D NaN NaN NaN NaN NaN NaN NaN NaN NaN
14 7090 G1 2004-09-19 NaN Olympiakos Kalithea 3 0 H 2 0 H NaN NaN NaN NaN NaN NaN NaN NaN NaN
Essentially, I want to place non null values of HT and AT to HomeTeam and AwayTeam columns
There does not seem to be a straightforward way; there are many ways I guess;
Create a new column with an IF HT and AT not blank and HomeTeam and
AwayTeam blank then HT and AT else HomeTeam and AwayTeam
If (In column HomeTeam and AwayTeam) If HomeTeam and AwayTeam blank then HT
and AT else HomeTeam and Away Team.
How can I go about it in pandas?
You can do this considering df is your pandas Dataframe and you have imported NumPy as np
df = df.replace('', np.nan)
And after that apply a lambda function looking for the 'NaN' value like in the code below:
import pandas as pd
names = {'First_name': ['Jon','Bill','Maria','Emma']}
df = pd.DataFrame(names,columns=['First_name'])
df['name_match'] = df['First_name'].apply(lambda x: 'Match' if x == 'Bill' else 'Mismatch')
print (df)

Why does pandas.interpolate() interpolate single values surrounded by NaNs?

I have a problem with pandas interpolate(). I only want to interpolate when there are not more than 2 succsessive "np.nans".
But the interpolate function tries to interpolate also single values when there are more than 2 np.nans!?
s = pd.Series(data = [np.nan,10,np.nan,np.nan,np.nan,5,np.nan,6,np.nan,np.nan,30])
a = s.interpolate(limit=2,limit_area='inside')
print(a)
the output I get is:
0 NaN
1 10.00
2 8.75
3 7.50
4 NaN
5 5.00
6 5.50
7 6.00
8 14.00
9 22.00
10 30.00
dtype: float64
I do not want the result in line 2 and 3.
What I want is:
0 NaN
1 10.00
2 NaN
3 NaN
4 NaN
5 5.00
6 5.50
7 6.00
8 14.00
9 22.00
10 30.00
dtype: float64
Can anybody please help?
Groupby.transform with Series.where
s_notna = s.notna()
m = (s.groupby(s_notna.cumsum()).transform('size').le(3) | s_notna)
s = s.interpolate(limit_are='inside').where(m)
print(s)
Output
0 NaN
1 10.0
2 NaN
3 NaN
4 NaN
5 5.0
6 5.5
7 6.0
8 14.0
9 22.0
10 30.0
dtype: float64

Range (list) as dummy columns

I have two columns with start and end range. I want make dummy columns for range between this columns. I can make it by apply method, but it is very slow. Can I make it without apply (because I have ~2-5M rows).
Entire DataFrame:
start end
0 36 36
1 31 31
2 29 29
3 10 10
4 35 35
5 42 44
6 24 26
What I want to see:
start end 8 9 10 24 25 26 29 31 35 36 42 43 44
0 36 36 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN
1 31 31 NaN NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN NaN NaN
2 29 29 NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN NaN NaN NaN
3 10 10 NaN NaN 1.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 35 35 NaN NaN NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
5 42 44 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.0 1.0 1.0
6 24 26 NaN NaN NaN 1.0 1.0 1.0 NaN NaN NaN NaN NaN NaN NaN
7 25 25 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN NaN NaN NaN NaN
8 35 35 NaN NaN NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
9 8 10 1.0 1.0 1.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Now I use this code:
import itertools
def zip_with_scalar(l, o):
return dict(zip(l, itertools.repeat(o)))
df.merge(df.apply(lambda s: pd.Series(zip_with_scalar(range(s['start'], s['end']+1), 1)), axis = 1), left_index=True, right_index=True)
Use list comprehension with DataFrame constructor:
a = [dict.fromkeys(range(x, y), 1) for x, y in zip(df['start'], df['end']+1)]
df = df.join(pd.DataFrame(a, index=df.index))
print (df)
start end 10 24 25 26 29 31 35 36 42 43 44
0 36 36 NaN NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN
1 31 31 NaN NaN NaN NaN NaN 1.0 NaN NaN NaN NaN NaN
2 29 29 NaN NaN NaN NaN 1.0 NaN NaN NaN NaN NaN NaN
3 10 10 1.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 35 35 NaN NaN NaN NaN NaN NaN 1.0 NaN NaN NaN NaN
5 42 44 NaN NaN NaN NaN NaN NaN NaN NaN 1.0 1.0 1.0
6 24 26 NaN 1.0 1.0 1.0 NaN NaN NaN NaN NaN NaN NaN
Performance:
#[70000 rows x 2 columns]
df = pd.concat([df] * 10000, ignore_index=True)
def a(df):
a = [dict.fromkeys(range(x, y), 1) for x, y in zip(df['start'], df['end']+1)]
return df.join(pd.DataFrame(a, index=df.index))
import itertools
def zip_with_scalar(l, o):
return dict(zip(l, itertools.repeat(o)))
def b(df):
return df.merge(df.apply(lambda s: pd.Series(zip_with_scalar(range(s['start'], s['end']+1), 1)), axis = 1), left_index=True, right_index=True)
In [176]: %timeit a(df.copy())
202 ms ± 6.05 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [177]: %timeit b(df.copy())
38.9 s ± 1.19 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

Pandas combine two columns

I have following database:
df = pandas.DataFrame({'Buy':[10,np.nan,2,np.nan,np.nan,4],'Sell':[np.nan,7,np.nan,9,np.nan,np.nan]})
Out[37]:
Buy Sell
0 10.0 NaN
1 NaN 7.0
2 2.0 NaN
3 NaN 9.0
4 NaN NaN
5 4.0 NaN
I want o create two more columns called Quant and B/S
for Quant it is working fine as follows:
df['Quant'] = df['Buy'].fillna(df['Sell']) # Fetch available value from both column and if both values are Nan then output is Nan.
Output is:
df
Out[39]:
Buy Sell Quant
0 10.0 NaN 10.0
1 NaN 7.0 7.0
2 2.0 NaN 2.0
3 NaN 9.0 9.0
4 NaN NaN NaN
5 4.0 NaN 4.0
But I want to create B/S on the basis of "from which column they have taken value while creating Quant"
You can perform an equality test and feed into numpy.where:
df['B/S'] = np.where(df['Quant'] == df['Buy'], 'B', 'S')
For the case where both values are null, you can use an additional step:
df.loc[df[['Buy', 'Sell']].isnull().all(1), 'B/S'] = np.nan
Example
from io import StringIO
import pandas as pd
mystr = StringIO("""Buy Sell
10 nan
nan 8
4 nan
nan 5
nan 7
3 nan
2 nan
nan nan""")
df = pd.read_csv(mystr, delim_whitespace=True)
df['Quant'] = df['Buy'].fillna(df['Sell'])
df['B/S'] = np.where(df['Quant'] == df['Buy'], 'B', 'S')
df.loc[df[['Buy', 'Sell']].isnull().all(1), 'B/S'] = np.nan
Result
print(df)
Buy Sell Quant B/S
0 10.0 NaN 10.0 B
1 NaN 8.0 8.0 S
2 4.0 NaN 4.0 B
3 NaN 5.0 5.0 S
4 NaN 7.0 7.0 S
5 3.0 NaN 3.0 B
6 2.0 NaN 2.0 B
7 NaN NaN NaN NaN

Categories

Resources