Replacing values with nan based on values of another column - python

This is my dataframe:
df = pd.DataFrame(
{
'a': [np.nan, np.nan, np.nan, 3333, np.nan, np.nan, 10, np.nan, np.nan, np.nan, np.nan, 200, 100],
'b': [np.nan, 20, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 100, np.nan, np.nan, np.nan, np.nan]
}
)
And this is the output that I want:
a b
0 NaN NaN
1 NaN 20.0
2 NaN NaN
3 3333.0 NaN
4 NaN NaN
5 NaN NaN
6 NaN NaN
7 NaN NaN
8 NaN 100.0
9 NaN NaN
10 NaN NaN
11 200.0 NaN
12 NaN NaN
Basically if a value in column 'b' is not NaN, I want to keep one value in column a. And then make the rest of values in column a NaN until a value in column b is not NaN.
For example the first case is 20 in column b. After that I want to keep 3333 because this is one value below it which is not NaN and I want to replace 10 with NaN because I've already got one value below b which in this case is 3333 and it is not NaN. The same applies for 100 in column b.
I've searched many posts on stackoverflow and also tried a couple of lines but it didn't work. I guess maybe it can be done by fillna.

One approach
a_notna = df['a'].notna()
m = (a_notna.groupby(df['b'].notna().cumsum())
.cumsum()
.eq(1) & a_notna)
df['a'] = df['a'].where(m)
print(df)
a b
0 NaN NaN
1 NaN 20.0
2 NaN NaN
3 3333.0 NaN
4 NaN NaN
5 NaN NaN
6 NaN NaN
7 NaN NaN
8 NaN 100.0
9 NaN NaN
10 NaN NaN
11 200.0 NaN
12 NaN NaN

Related

How to create columns by looking not null values in other columns

I have the dataframe, that needs to put the not null values into the column.
For example: there maybe more than 5 columns, but no more than 2 not null values each rows
df1 = pd.DataFrame({'A' : [np.nan, np.nan, 'c',np.nan, np.nan, np.nan],
'B' : [np.nan, np.nan, np.nan, 'a', np.nan,'e'],
'C' : [np.nan, 'b', np.nan,'f', np.nan, np.nan],
'D' : [np.nan, np.nan, 'd',np.nan, np.nan, np.nan],
'E' : ['a', np.nan, np.nan,np.nan, np.nan, 'a']})
A B C D E
NaN NaN NaN NaN a
NaN NaN b NaN NaN
c NaN NaN d NaN
NaN a f NaN NaN
NaN NaN NaN NaN NaN
NaN e NaN NaN a
My expected output: To generate 4 new columns, Other_1; Other_1_name; Other_2; Other_2_name, the value will go to Other_1 or Other_2 if there are not null values, and the column name will go to Other_1_name or Other_2_name. if the value is NaN leave the 4 column rows NaN.
A B C D E Other_1 Other_1_name Other_2 Other_2_name
NaN NaN NaN NaN a a E NaN NaN
NaN NaN b NaN NaN b C NaN NaN
c NaN NaN d NaN c A d D
NaN a f NaN NaN a B f C
NaN NaN NaN NaN NaN NaN NaN NaN NaN
NaN e NaN NaN a e B a E
Use DataFrame.melt with missing values by DataFrame.dropna for unpivot, then add counter columns by GroupBy.cumcount and reshape by DataFrame.unstack:
df2 = df1.melt(ignore_index=False,var_name='name',value_name='val').dropna()[['val','name']]
g = df2.groupby(level=0).cumcount().add(1)
df2 = df2.set_index(g,append=True).unstack().sort_index(level=1,axis=1,sort_remaining=False)
df2.columns = df2.columns.map(lambda x: f'Other_{x[1]}_{x[0]}')
print (df2)
Other_1_val Other_1_name Other_2_val Other_2_name
0 a E NaN NaN
1 b C NaN NaN
2 c A d D
3 a B f C
5 e B a E
Last append to original:
df = df1.join(df2)
print (df)
A B C D E Other_1_val Other_1_name Other_2_val Other_2_name
0 NaN NaN NaN NaN a a E NaN NaN
1 NaN NaN b NaN NaN b C NaN NaN
2 c NaN NaN d NaN c A d D
3 NaN a f NaN NaN a B f C
4 NaN NaN NaN NaN NaN NaN NaN NaN NaN
5 NaN e NaN NaN a e B a E

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

What is the difference between swaplevel() and reorder_levels()?

While working with hierarchical index levels in pandas, what is the difference between swaplevel() and reorder_levels()?
When there only two levels swaplevel and reorder_levels almost same , but when your df have more than 3 levels , personally think reorder_levels is more elegant way
For example :
idx = pd.MultiIndex.from_arrays([[1, 1, 2], [1, 2, 2], [3, 3, 3],[1,1,1]])
df = pd.DataFrame(columns=idx, index=[1, 2, 3, 4])
IF we want to change the order level=[0,1,2,3] to [3,2,1,0]
With swaplevel : need multiple calls
df.swaplevel(0,3,axis=1).swaplevel(1,2,axis=1)
1
3
1 2
1 1 2
1 NaN NaN NaN
2 NaN NaN NaN
3 NaN NaN NaN
4 NaN NaN NaN
With reorder_levels : Only one call
df.reorder_levels([3,2,1,0],axis=1)
1
3
1 2
1 1 2
1 NaN NaN NaN
2 NaN NaN NaN
3 NaN NaN NaN
4 NaN NaN NaN

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

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()

Categories