pandas groupby: *full* join result of groupwise operation on original index - python

Consider this df:
import pandas as pd, numpy as np
df = pd.DataFrame.from_dict({'id': ['A', 'B', 'A', 'C', 'D', 'B', 'C'],
'val': [1,2,-3,1,5,6,-2],
'stuff':['12','23232','13','1234','3235','3236','732323']})
Question: how to produce a table with as many columns as unique id ({A, B, C}) and
as many rows as df where, for example for the column corresponding to id==A, the values are:
1,
np.nan,
-2,
np.nan,
np.nan,
np.nan,
np.nan
(that is the result of df.groupby('id')['val'].cumsum() joined on the indexes of df).

UMMM pivot
pd.pivot(df.index,df.id,df.val).cumsum()
Out[33]:
id A B C D
0 1.0 NaN NaN NaN
1 NaN 2.0 NaN NaN
2 -2.0 NaN NaN NaN
3 NaN NaN 1.0 NaN
4 NaN NaN NaN 5.0
5 NaN 8.0 NaN NaN
6 NaN NaN -1.0 NaN

One way via a dictionary comprehension and pd.DataFrame.where:
res = pd.DataFrame({i: df['val'].where(df['id'].eq(i)).cumsum() for i in df['id'].unique()})
print(res)
A B C D
0 1.0 NaN NaN NaN
1 NaN 2.0 NaN NaN
2 -2.0 NaN NaN NaN
3 NaN NaN 1.0 NaN
4 NaN NaN NaN 5.0
5 NaN 8.0 NaN NaN
6 NaN NaN -1.0 NaN
For a small number of groups, you may find this method efficient:
df = pd.concat([df]*1000, ignore_index=True)
def piv_transform(df):
return pd.pivot(df.index, df.id, df.val).cumsum()
def dict_transform(df):
return pd.DataFrame({i: df['val'].where(df['id'].eq(i)).cumsum() for i in df['id'].unique()})
%timeit piv_transform(df) # 17.5 ms
%timeit dict_transform(df) # 8.1 ms

Certainly cleaner answers have been supplied - see pivot.
df1 = pd.DataFrame( data = [df.id == x for x in df.id.unique()]).T.mul(df.groupby(['id']).cumsum().squeeze(),axis=0)
df1.columns =df.id.unique()
df1.applymap(lambda x: np.nan if x == 0 else x)
A B C D
0 1.0 NaN NaN NaN
1 NaN 2.0 NaN NaN
2 -2.0 NaN NaN NaN
3 NaN NaN 1.0 NaN
4 NaN NaN NaN 5.0
5 NaN 8.0 NaN NaN
6 NaN NaN -1.0 NaN

Short and simple:
df.pivot(columns='id', values='val').cumsum()

Related

append specific amount of empty rows to pandas dataframe

I want to append a specific amount of empty rows to that df
df = pd.DataFrame({'cow': [2, 4, 8],
'shark': [2, 0, 0],
'pudle': [10, 2, 1]})
with df = df.append(pd.Series(), ignore_index = True) I append one empty row, how can I append x amount of rows ?
You can use df.reindex to achieve this goal.
df.reindex(list(range(0, 10))).reset_index(drop=True)
cow shark pudle
0 2.0 2.0 10.0
1 4.0 0.0 2.0
2 8.0 0.0 1.0
3 NaN NaN NaN
4 NaN NaN NaN
5 NaN NaN NaN
6 NaN NaN NaN
7 NaN NaN NaN
8 NaN NaN NaN
9 NaN NaN NaN
The arguments you provide to df.reindex is going to be the total number of rows the new DataFrame has. So if your DataFrame has 3 objects, providing a list that caps out at 10 will add 7 new rows.
I'm not too pandas savvy, but if you can already add one empty row, why not just try writing a for loop and appending x times?
for i in range(x):
df = df.append(pd.Series(), ignore_index = True)
You could do:
import pandas as pd
df = pd.DataFrame({'cow': [2, 4, 8],
'shark': [2, 0, 0],
'pudle': [10, 2, 1]})
n = 10
df = df.append([[] for _ in range(n)], ignore_index=True)
print(df)
Output
cow shark pudle
0 2.0 2.0 10.0
1 4.0 0.0 2.0
2 8.0 0.0 1.0
3 NaN NaN NaN
4 NaN NaN NaN
5 NaN NaN NaN
6 NaN NaN NaN
7 NaN NaN NaN
8 NaN NaN NaN
9 NaN NaN NaN
10 NaN NaN NaN
11 NaN NaN NaN
12 NaN NaN NaN
Try with reindex
out = df.reindex(df.index.tolist()+[df.index.max()+1]*5)#reset_index(drop=True)
Out[93]:
cow shark pudle
0 2.0 2.0 10.0
1 4.0 0.0 2.0
2 8.0 0.0 1.0
3 NaN NaN NaN
3 NaN NaN NaN
3 NaN NaN NaN
3 NaN NaN NaN
3 NaN NaN NaN
Create an empty dataframe of the appropriate size and append it:
import numpy as np
df = df.append(pd.DataFrame([[np.nan] * df.shape[1]] * n,columns=df.columns),
ignore_index = True)

How to set index NaN (empty cell )

gc = gspread.authorize(creds)
ws = gc.open("Data").worksheet("test3")
df = get_as_dataframe(ws).set_index('A')
# update
df._set_value('Bat', 'B', '11')
df._set_value('Bat', 'C', '12')
df._set_value('Bat', 'D', '13')
df.loc[ str('Fog')] = ''
df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
print(df)
output
B C D
A
Cat 5 6 9
Dog 3 1 7
Bat 11 12 13
NaN NaN NaN NaN
NaN NaN NaN NaN
.. ... ... ...
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
Fog
and I want to be like this ....have 3 index Cat,Dog,Bat and I want to set NaN cell to new index name Fog after change value in Bat index
B C D
A
Cat 5 6 9
Dog 3 1 7
Bat 11 12 13
Fog NaN NaN NaN
NaN NaN NaN NaN
.. ... ... ...
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
Here is my approach, I get the datafame without NaN index and append 'Fog' to it. Then, I add the NaN rows to the above-mentioned dataframe using append:
import io
import pandas as pd
import numpy as np
#Creation of an example of dataframe
s_e='''
A B C D
Cat 5 6 9
Dog 3 1 7
Bat 11 12 13
'''
df= pd.read_csv(io.StringIO(s_e), sep='\s\s+', engine='python')
df=df.set_index('A')
df._set_value('Bat', 'B', '11')
df._set_value('Bat', 'C', '12')
df._set_value('Bat', 'D', '13')
for i in range(5):
df1 = pd.DataFrame([[np.nan] * len(df.columns)], columns=df.columns, index=[np.nan])
df=df.append(df1)
df=df.rename_axis("A")
print(df)
#Adding the row 'Fog'
df1=df[~df.index.isna()].append(pd.DataFrame([[np.nan] * len(df.columns)], columns=df.columns, index=['Fog']))
df=df1.append(df[df.index.isna()])
df=df.rename_axis("A")
print(df)
Output:
df:
B C D
A
Cat 5.0 6.0 9.0
Dog 3.0 1.0 7.0
Bat 11.0 12.0 13.0
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
newdf:
B C D
A
Cat 5.0 6.0 9.0
Dog 3.0 1.0 7.0
Bat 11.0 12.0 13.0
Fog NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN
NaN NaN NaN NaN

How to ask a generated pivot table to include all column combination possibilities?

When use the Python pivot tables, I would like to include all column combination possibilities. For example:
import pandas as pd
from pandas import DataFrame
Result ={
'SenderUserId': ['a', 'a', 'b', 'c', 'c'],
'Date': ['1', '2', '2', '3', '4'],
'RecipientUserId': ['m', 'm', 'n', 'n', 'z'],
'nmail':[1, 2, 3, 3,7]
}
result = DataFrame (Result, columns = ['SenderUserId', 'Date', 'RecipientUserId', 'nmail'])
result = result.pivot_table(index=['SenderUserId'], columns =['Date', 'RecipientUserId'], values = 'nmail').stack()
print (result.head ())
will be producing the following results:
Date 1 2 3 4
SenderUserId RecipientUserId
a m 1.0 2.0 NaN NaN
b n NaN 3.0 NaN NaN
c n NaN NaN 3.0 NaN
z NaN NaN NaN 7.0
However, what I really wanted to get was something like:
Date 1 2 3 4
SenderUserId RecipientUserId
a m 1.0 2.0 NaN NaN
n NaN NaN NaN NaN
z NaN NaN NaN NaN
b m NaN NaN NaN NaN
n NaN 3.0 NaN NaN
z NaN NaN NaN NaN
c m NaN NaN NaN NaN
n NaN NaN 3.0 NaN
z NaN NaN NaN 7.0
As you can see, we just add a few lines where there is no initial column match and assign them NaN. That's okay. As long as this question can be solved, I don't necessarily need to use pivot_table. Any help would be really appreciated!
All the extra rows you're asking for will necessarily be all NaN, so you can just add them in at the end. After doing the pivot, you can use the technique described here to "expand" the resulting index to all possible combinations.
pivot = result.pivot_table(index=['SenderUserId'], columns=['Date', 'RecipientUserId'], values='nmail').stack()
pivot.reindex(pd.MultiIndex.from_product(pivot.index.levels, names=pivot.index.names))
The result:
Date 1 2 3 4
SenderUserId RecipientUserId
a m 1.0 2.0 NaN NaN
n NaN NaN NaN NaN
z NaN NaN NaN NaN
b m NaN NaN NaN NaN
n NaN 3.0 NaN NaN
z NaN NaN NaN NaN
c m NaN NaN NaN NaN
n NaN NaN 3.0 NaN
z NaN NaN NaN 7.0

Join several dataframes on an empty dataframe with fixed index, merging columns or appending those

I have a dataframe with a range index and no data, in real data the index is a time range.
E.g.
df_main = pd.DataFrame(index = pd.RangeIndex(0,15,1))
See Fig1
And I have several dataframes which varying columns and indexes, I just want to join those on the main dataframe based on index:
df1 = pd.DataFrame({'value': [1, 2, 3, 5]}, index = pd.RangeIndex(0,4,1))
df2 = pd.DataFrame({'value': [5, 6, 7, 8]}, index = pd.RangeIndex(4,8,1))
df3 = pd.DataFrame({'value2': [9, 8, 7, 6]}, index = pd.RangeIndex(0,4,1))
df4 = pd.DataFrame({'value': [1, 2],'value2': [3, 4],'value3': [5, 6]}, index = pd.RangeIndex(10,12,1))
See Fig 2,3,4,5
I tried concat:
display(pd.concat([df_main,df1,df2,df3,df4]))
Which gives me the unwanted output you can see in Fig 6.
I also tried join which results in an error I did not understand:
ValueError: Indexes have overlapping values: Index(['value', 'value2'], dtype='object')
What I want to is the output you can see in Fig7.
You could groupby the index and aggregate with first:
pd.concat([df_main, df1, df2, df3, df4]).groupby(level=0).first()
[out]
value value2 value3
0 1.0 9.0 NaN
1 2.0 8.0 NaN
2 3.0 7.0 NaN
3 5.0 6.0 NaN
4 5.0 NaN NaN
5 6.0 NaN NaN
6 7.0 NaN NaN
7 8.0 NaN NaN
8 NaN NaN NaN
9 NaN NaN NaN
10 1.0 3.0 5.0
11 2.0 4.0 6.0
12 NaN NaN NaN
13 NaN NaN NaN
14 NaN NaN NaN
Use reduce and DataFrame.combine_first:
from functools import reduce
df = reduce((lambda x, y: x.combine_first(y)), [df_main,df1,df2,df3,df4])
print(df)
value value2 value3
0 1.0 9.0 NaN
1 2.0 8.0 NaN
2 3.0 7.0 NaN
3 5.0 6.0 NaN
4 5.0 NaN NaN
5 6.0 NaN NaN
6 7.0 NaN NaN
7 8.0 NaN NaN
8 NaN NaN NaN
9 NaN NaN NaN
10 1.0 3.0 5.0
11 2.0 4.0 6.0
12 NaN NaN NaN
13 NaN NaN NaN
14 NaN NaN NaN

Move/shift values in Pandas Data Frame

Assuming an example of a data frame df:
A
0 4.3
1 75
2 8.5
3 4.0
4 98
I would need to move each value from column A to each column - one value per column:
starting from second value: move to second column B,
third value to third column C,
and so on...
Desired output:
A B C D E
0 4.3 NaN NaN NaN NaN
1 NaN 75 NaN NaN NaN
2 NaN NaN 8.5 NaN NaN
3 NaN NaN NaN 4.0 NaN
4 NaN NaN NaN Nan 98
One idea was to copy each value to second column and then erase it in previous column or to shift value from one column to another but I'm not sure how to apply this...
MWE
import pandas as pd
import numpy as np
df=pd.DataFrame(data=np.random.randint(0,100,(5,5)), columns=['A','B','C','D','E'])
df.iloc[:,1:] =np.nan
df.iloc[[1],[1]] = df.iloc[[1],[0]]
df.iloc[[1],[1]] = df.iloc[[1],[0]].shift(1,axis=1)
In [76]: import string
In [77]: r = pd.DataFrame(np.eye(len(df)),
columns=list(string.ascii_uppercase[:len(df)])) \
.replace(0, np.nan) * df.A.values
In [78]: r
Out[78]:
A B C D E
0 4.3 NaN NaN NaN NaN
1 NaN 75.0 NaN NaN NaN
2 NaN NaN 8.5 NaN NaN
3 NaN NaN NaN 4.0 NaN
4 NaN NaN NaN NaN 98.0
or better:
In [11]: r = pd.DataFrame(index=df.index, columns=list(string.ascii_uppercase[:len(df)]))
In [12]: np.fill_diagonal(r.values, df.A)
In [13]: r
Out[13]:
A B C D E
0 4.3 NaN NaN NaN NaN
1 NaN 75 NaN NaN NaN
2 NaN NaN 8.5 NaN NaN
3 NaN NaN NaN 4 NaN
4 NaN NaN NaN NaN 98
UPDATE:
how to "move" single value
we can use Series.shift method.
move horizontally:
In [94]: r.loc[1] = r.loc[1].shift(3)
In [95]: r
Out[95]:
A B C D E
0 4.3 NaN NaN NaN NaN
1 NaN NaN NaN NaN 75.0
2 NaN NaN 8.5 NaN NaN
3 NaN NaN NaN 4.0 NaN
4 NaN NaN NaN NaN 98.0
move vertically:
In [96]: r.loc[:, 'D'] = r.loc[:, 'D'].shift(-2)
In [97]: r
Out[97]:
A B C D E
0 4.3 NaN NaN NaN NaN
1 NaN NaN NaN 4.0 75.0
2 NaN NaN 8.5 NaN NaN
3 NaN NaN NaN NaN NaN
4 NaN NaN NaN NaN 98.0
NOTE: shift will shift the whole row/column, but as soon as we have only one value in each row/column this will work.
>>> import pandas as pd
>>> import numpy as np
>>> df = pd.DataFrame({'A':[4.3, 75, 8.5, 4.0, 98]})
>>> df
A
0 4.3
1 75.0
2 8.5
3 4.0
4 98.0
>>> diag_df = pd.DataFrame(np.diag(df.A), index=df.index, columns=['A', 'B', 'C', 'D', 'E'])
>>> diag_df.replace(0, np.nan, inplace=True)
>>> diag_df
A B C D E
0 4.3 NaN NaN NaN NaN
1 NaN 75.0 NaN NaN NaN
2 NaN NaN 8.5 NaN NaN
3 NaN NaN NaN 4.0 NaN
4 NaN NaN NaN NaN 98.0
Keep in mind that if you have 0 along the diagonal then it will be replaced with NaN if you use the replace method this way.

Categories