Reputation: 323
I have a large DataFrame that looks like this:
+----------------------------------------------------------+
| Date Category Location ImpactRate |
+----------------------------------------------------------+
| 2018-04-22 Outage MT 0.05194 |
| 2018-04-22 Outage ND 0.02552 |
| 2018-04-22 Outage SD 0.09962 |
| 2018-04-24 Transport TX 0.03111 |
+----------------------------------------------------------+
What I'm trying to do is create the following output:
+-----------------------------------------------------------------------------------+
| Date Category ImpactRate Break Down |
+-----------------------------------------------------------------------------------+
| 2018-04-22 Outage 0.17708 MT (29.3%) SD (14.4%) ND (56.3%) |
| 2018-04-24 Transport 0.03111 TX (100.0%) |
+-----------------------------------------------------------------------------------+
First Attempt - GroupBy
grouped_df = df.groupby('Date')['ImpactRate'].apply(list).apply(pd.Series).rename(columns=df['Location'])'
This creates a DataFrame that will contain NaNs where each Location isn't present and creates additional columns that require formatting.
Second Attempt - Loop using itertuples():
r = []
for item in df.itertuples():
temp_x = df.loc[((df['Category'] == item[2]) & (df['Date'] == item[1]))
for i in range(temp_x.shape[0]):
r.append(temp_x['ImpactRate'].iloc[i])
This creates one giant list of ImpactRate - which takes me back to square one.
I don't know enough to understand how to troubleshoot this. I'm guessing I should make a list within a list for each iteration but I keep going in circles. How can I achieve this output in the most pythonic way? (Please explain so that I can learn!)
Upvotes: 2
Views: 262
Reputation: 294218
I don't feel terribly good about this.. but oh well.
d = pd.Series(
df.ImpactRate.values,
[list(zip(df.Date, df.Category)), df.Location],
name='Impact Rate'
)
s = d.sum(level=0)
t = d.div(s, level=0).rename('Break Down')
f = lambda t: ' '.join(f'{l} ({v*100:0.2f}%)' for (_, l), v in t.items())
pd.DataFrame(pd.concat(
[s, t.groupby(level=0).apply(f)], axis=1
).to_dict()).rename_axis(['Date', 'Category']).reset_index()
Date Category Break Down Impact Rate
0 2018-04-22 Outage MT (29.33%) ND (14.41%) SD (56.26%) 0.17708
1 2018-04-24 Transport TX (100.00%) 0.03111
Upvotes: 1
Reputation: 164623
This is one way using vectorised functionality revolving around groupby.transform
. I have defined Breakdown
series as lists of tuples, as this is the most flexible format. You can apply specific string formats if you wish.
import pandas as pd
df = pd.DataFrame({'Date': ['2018-04-22', '2018-04-22', '2018-04-22', '2018-04-24'],
'Category': ['Outage', 'Outage', 'Outage', 'Transport'],
'Location': ['MT', 'ND', 'SD', 'TX'],
'ImpactRate': [0.05194, 0.02552, 0.09962, 0.03111]})
# apply ratio
df['Total'] = df.groupby(['Date', 'Category'])['ImpactRate'].transform('sum')
df['ImpactRate'] /= df['Total']
# create tuple column
df['Breakdown'] = list(zip(df.Location, df.ImpactRate))
# groupby to list
df = df.groupby(['Category', 'Date', 'Total'])['Breakdown'].apply(list).reset_index()
Result:
print(df)
Category Date Total \
0 Outage 2018-04-22 0.17708
1 Transport 2018-04-24 0.03111
Breakdown
0 [(MT, 0.293313756494), (ND, 0.144115653942), (...
1 [(TX, 1.0)]
Upvotes: 1