How to stack and rename N successive columns in df - pandas

How would I achieve the desired output as shown below? Ie, stack the first 3 columns underneath each other, stack the second 3 columns underneath each other and rename the columns.
d = {'A': [76, 34], 'B': [21, 48], 'C': [45, 89], 'D': [56, 41], 'E': [3, 2],
'F': [78, 32]}
df = pd.DataFrame(data=d)
df.columns=['A', 'A', 'A', 'A', 'A', 'A']
Output
df
A A A A A A
0 76 21 45 56 3 78
1 34 48 89 41 2 32
Desired Output
Z1 Z2
0 76 56
1 34 41
2 21 3
3 48 2
4 45 78
5 89 32

Go down into numpy, reshape and create a new dataframe:
pd.DataFrame(df.to_numpy().reshape((-1, 2), order='F'), columns = ['Z1','Z2'])
Out[19]:
Z1 Z2
0 76 56
1 34 41
2 21 3
3 48 2
4 45 78
5 89 32

Related

Convert a Pandas DataFrame into a multiple rows

I have a dataframe with "N" rows and 4 columns (N,4).I want to make something like this (1, 4.N). I would like (4,4) to become a row every time
For example if I have:
A B C D
1, 5, 9, 13
2, 6, 10, 14
3, 7, 11, 15
4, 8, 12, 16
1, 11, 56, 9
1, 34, 87, 91
67, 67, 9, 1
1, 37, 77, 9
I want a result like this:
A1 B1 C1 D1 A2 B2 C2 D2 A3 B3 C3 D3 A4 B4 C4 D4
1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16
1 11 56 9 1 34 87 91 67 67 9 1 1 37 77 9
You can do it using reshape from numpy:
import pandas as pd
from io import StringIO
df = pd.read_csv(
StringIO(
"""
1, 5, 9, 13
2, 6, 10, 14
3, 7, 11, 15
4, 8, 12, 16
1, 11, 56, 9
1, 34, 87, 91
67, 67, 9, 1
1, 37, 77, 9 """
)
)
pd.DataFrame(
df.values.reshape(2, 16),
columns=[f"{i}{n}" for n in range(1, 5) for i in ["A", "B", "C", "D"]],
)
This will return:
A1 B1 C1 D1 A2 B2 C2 D2 A3 B3 C3 D3 A4 B4 C4 D4
0 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16
1 1 11 56 9 1 34 87 91 67 67 9 1 1 37 77 9
as requested.

How to compute mean for different size subsets within pandas dataframe?

compute mean of particular column for each unique subset of rows in pandas dataframe. In following example each subset is till 1 appears in column "Flag" i.e. (54+34+78+91+29)/5 = 57.2 and (81+44+61)/3 = 62.0
Currently unable to compute rolling subset of different sizes based on particular column condition
>>> import pandas as pd
>>> df = pd.DataFrame({"Indx": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "Units": [54, 34, 78, 91, 29, 81, 44, 61, 73, 19], "Flag": [0, 0, 0, 0, 1, 0, 0, 1, 0, 1]})
>>> df
Indx Units Flag
0 1 54 0
1 2 34 0
2 3 78 0
3 4 91 0
4 5 29 1
5 6 81 0
6 7 44 0
7 8 61 1
8 9 73 0
9 10 19 1
# DESIRED OUTPUT
>>> df
Indx Units Flag avg
0 1 54 0 57.2
1 2 34 0 57.2
2 3 78 0 57.2
3 4 91 0 57.2
4 5 29 1 57.2
5 6 81 0 62.0
6 7 44 0 62.0
7 8 61 1 62.0
8 9 73 0 46.0
9 10 19 0 46.0
Create the group key by using cumsum then transform
df['Units'].groupby(df.Flag.iloc[::-1].cumsum()).transform('mean')
0 57.2
1 57.2
2 57.2
3 57.2
4 57.2
5 62.0
6 62.0
7 62.0
8 46.0
9 46.0
Name: Units, dtype: float64
#df['new']=df['Units'].groupby(df.Flag.iloc[::-1].cumsum()).transform('mean')
The shortest solution (I think) is:
df['avg'] = df.groupby(df.Flag[::-1].cumsum()).Units.transform('mean')
You don't even need to use iloc, as df.Flag[::-1] retrieves Flag
column in reversed order.

MultiIndex isn't kept when pd.concating multiple subtotal rows

I lose my multiIndex when I try to pd.concat a second subtotal. I'm able to add the first subtotal but not the second which is a sum of B0.
This is how my current df is:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
First Total 6 4 10 8
C1 D0 9 8 11 10
D1 13 12 15 14
First Total 22 20 26 24
C2 D0 17 16 19 18
After trying to add the second subtotal I get this:
lvl0 a b
lvl1 bar foo bah foo
(A0, B0, C2, First Total) 38 36 42 40
(A0, B0, C3, D0) 25 24 27 26
(A0, B0, C3, D1) 29 28 31 30
(A0, B0, C3, First Total) 54 52 58 56
(A0, B0, Second Total) 120 112 136 128
(A0, B1, C0, D0) 33 32 35 34
(A0, B1, C0, D1) 37 36 39 38
(A0, B1, C0, First Total) 70 68 74 72
(A0, B1, C1, D0) 41 40 43 42
You should be able to copy and paste the code below to test
import pandas as pd
import numpy as np
# creating multiIndex
def mklbl(prefix, n):
return ["%s%s" % (prefix, i) for i in range(n)]
miindex = pd.MultiIndex.from_product([mklbl('A', 4),
mklbl('B', 2),
mklbl('C', 4),
mklbl('D', 2)])
micolumns = pd.MultiIndex.from_tuples([('a', 'foo'), ('a', 'bar'),
('b', 'foo'), ('b', 'bah')],
names=['lvl0', 'lvl1'])
dfmi = pd.DataFrame(np.arange(len(miindex) * len(micolumns))
.reshape((len(miindex), len(micolumns))),
index=miindex,
columns=micolumns).sort_index().sort_index(axis=1)
# My code STARTS HERE
# creating the first subtotal
print(dfmi.index)
df1 = dfmi.groupby(level=[0,1,2]).sum()
df2 = dfmi.groupby(level=[0, 1]).sum()
df1 = df1.set_index(np.array(['First Total'] * len(df1)), append=True)
dfmi = pd.concat([dfmi, df1]).sort_index(level=[0, 1])
print(dfmi)
# this is where the multiIndex is lost
df2 = df2.set_index(np.array(['Second Total'] * len(df2)), append=True)
dfmi = pd.concat([dfmi, df2]).sort_index(level=[1])
print(dfmi)
How I would want it to look:
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 D0 1 0 3 2
D1 5 4 7 6
First Total 6 4 10 8
C1 D0 9 8 11 10
D1 13 12 15 14
First Total 22 20 26 24
C2 D0 17 16 19 18
D1 21 20 23 22
First Total 38 36 42 40
C3 D0 25 24 27 26
D1 29 28 31 30
First Total 54 52 58 56
Second Total 120 112 136 128
B1 C0 D0 33 32 35 34
D1 37 36 39 38
First Total 70 68 74 72
C1 D0 41 40 43 42
D1 45 44 47 46
First Total 86 84 90 88
C2 D0 49 48 51 50
D1 53 52 55 54
First Total 102 100 106 104
C3 D0 57 56 59 58
D1 61 60 63 62
First Total 118 116 122 120
Second Total 376 368 392 384
the first total is sum of level 2,
the second total is sum of level 1
dfmi has a 4-level MultiIndex:
In [208]: dfmi.index.nlevels
Out[208]: 4
df2 has a 3-level MultiIndex. Instead, if you use
df2 = df2.set_index([np.array(['Second Total'] * len(df2)), [''] * len(df2)], append=True)
then df2 ends up with a 4-level MultiIndex. When dfmi and df2 have the same number of levels,
then pd.concat([dfmi, df2]) produces the desired result.
One problem you may face when sorting by index labels is that it relies on the strings 'First' and 'Second'
appearing last in alphabetic order. An alterative to sorting by index would be assigning a numeric order column
and sorting by that instead:
dfmi['order'] = range(len(dfmi))
df1['order'] = dfmi.groupby(level=[0,1,2])['order'].last() + 0.1
df2['order'] = dfmi.groupby(level=[0,1])['order'].last() + 0.2
...
dfmi = pd.concat([dfmi, df1, df2])
dfmi = dfmi.sort_values(by='order')
Incorporating Scott Boston's improvement, the code would then look like this:
import pandas as pd
import numpy as np
def mklbl(prefix, n):
return ["%s%s" % (prefix, i) for i in range(n)]
miindex = pd.MultiIndex.from_product([mklbl('A', 4),
mklbl('B', 2),
mklbl('C', 4),
mklbl('Z', 2)])
micolumns = pd.MultiIndex.from_tuples([('a', 'foo'), ('a', 'bar'),
('b', 'foo'), ('b', 'bah')],
names=['lvl0', 'lvl1'])
dfmi = pd.DataFrame(np.arange(len(miindex) * len(micolumns))
.reshape((len(miindex), len(micolumns))),
index=miindex,
columns=micolumns).sort_index().sort_index(axis=1)
df1 = dfmi.groupby(level=[0,1,2]).sum()
df2 = dfmi.groupby(level=[0, 1]).sum()
dfmi['order'] = range(len(dfmi))
df1['order'] = dfmi.groupby(level=[0,1,2])['order'].last() + 0.1
df2['order'] = dfmi.groupby(level=[0,1])['order'].last() + 0.2
df1 = df1.assign(lev4='First').set_index('lev4', append=True)
df2 = df2.assign(lev3='Second', lev4='').set_index(['lev3','lev4'], append=True)
dfmi = pd.concat([dfmi, df1, df2])
dfmi = dfmi.sort_values(by='order')
dfmi = dfmi.drop(['order'], axis=1)
print(dfmi)
which yields
lvl0 a b
lvl1 bar foo bah foo
A0 B0 C0 Z0 1 0 3 2
Z1 5 4 7 6
First 6 4 10 8
C1 Z0 9 8 11 10
Z1 13 12 15 14
First 22 20 26 24
C2 Z0 17 16 19 18
Z1 21 20 23 22
First 38 36 42 40
C3 Z0 25 24 27 26
Z1 29 28 31 30
First 54 52 58 56
Second 120 112 136 128
...
#unutbu points out the nature of the problem. df2 has three levels of a multiindex and you need a 4th level.
I would use assign and set_index to create that fourth level:
df2 = df2.assign(lev3='Second Total', lev4='').set_index(['lev3','lev4'], append=True)
This avoids calculating the length of the dataframe.

Merge/concatenate/Append dataframe pandas

I have two dataframes that look like this:
df1:
Index var1
0 56
1 67
2 21
Index var2
0 89
1 64
2 31
When I append or concatenate them, I get this:
Index var1 var2
0 56 nan
1 67 nan
2 21 nan
0 nan 89
1 nan 64
2 nan 31
But I would like to get this:
Index var1 var2
0 56 89
1 67 64
2 21 31
The commands I used are:
pd.concat([df1, df2], axis=1)
df1.append([df2])
EDIT:
This is a min-example:
df1 = pd.DataFrame({'var1' : [56,67,21]})
df2 = pd.DataFrame({'var2' : [89,64,31]})
df1.to_dict()
{'var1': {0: 56, 1: 67, 2: 21}}
df2.to_dict()
{'var2': {0: 89, 1: 64, 2: 31}}
df1.index.dtype
dtype('int64')
df2.index.dtype
dtype('int64')
Use:
df1 = df1.set_index('Index')
df2 = df2.set_index('Index')
pd.concat([df1,df2], axis=1)
NOTE: Besure that Index is in the index of the dataframe:
Output:
var1 var2
Index
0 56 89
1 67 64
2 21 31

pandas: conditionally select a row cell for each column based on a mask

I want to be able to extract values from a pandas dataframe using a mask. However, after searching around, I cannot find a solution to my problem.
df = pd.DataFrame(np.random.randint(0,2, size=(2,10)))
mask = np.random.randint(0,2, size=(1,10))
I basically want the mask to serve as a index lookup for each column.
So if the mask was [0,1] for columns [a,b], I want to return:
df.iloc[0,a], df.iloc[1,b]
but in a pythonic way.
I have tried e.g.:
df.apply(lambda x: df.iloc[mask[x], x] for x in range(len(mask)))
which gives a Type error that I don't understand.
A for loop can work but is slow.
With NumPy, that's covered as advanced-indexing and should be pretty efficient -
df.values[mask, np.arange(mask.size)]
Sample run -
In [59]: df = pd.DataFrame(np.random.randint(11,99, size=(5,10)))
In [60]: mask = np.random.randint(0,5, size=(1,10))
In [61]: df
Out[61]:
0 1 2 3 4 5 6 7 8 9
0 17 87 73 98 32 37 61 58 35 87
1 52 64 17 79 20 19 89 88 19 24
2 50 33 41 75 19 77 15 59 84 86
3 69 13 88 78 46 76 33 79 27 22
4 80 64 17 95 49 16 87 82 60 19
In [62]: mask
Out[62]: array([[2, 3, 0, 4, 2, 2, 4, 0, 0, 0]])
In [63]: df.values[mask, np.arange(mask.size)]
Out[63]: array([[50, 13, 73, 95, 19, 77, 87, 58, 35, 87]])