Pandas DataFrame and grouping Pandas Series data into individual columns by value - python

I am hoping someone can help me optimize the following Python/Pandas code. My code works, but I know there must be a cleaner and faster way to perform the operation under consideration.
I am looking for an optimized strategy because my use case will involve 16 unique ADC Types, as opposed to 4 in the example below. Also, my initial Pandas Series (i.e. ADC Type column), will be several 100,000 data points in length, rather than 8 in the example below.
import numpy as np
import pandas as pd
from enum import Enum
data_dict = {"RAW": [4000076160, 5354368, 4641792, 4289860736,
4136386944, 5440384, 4772864, 4289881216],
"ADC_TYPE": [3, 7, 8, 9,
3, 7, 8, 9]}
df = pd.DataFrame(data_dict)
print(df)
The initial DataFrame (i.e. df) is:
RAW ADC_TYPE
0 4000076160 3
1 5354368 7
2 4641792 8
3 4289860736 9
4 4136386944 3
5 5440384 7
6 4772864 8
7 4289881216 9
I then manipulate the DataFrame above using the following code:
unique_types = df["ADC_TYPE"].unique()
dict_concat = {"RAW": [],
"ADC_TYPE_3": [],
"ADC_TYPE_7": [],
"ADC_TYPE_8": [],
"ADC_TYPE_9": []}
df_concat = pd.DataFrame(dict_concat)
for adc_type in unique_types:
df_group = df.groupby(["ADC_TYPE"]).get_group(adc_type).rename(columns={"ADC_TYPE": f"ADC_TYPE_{adc_type}"})
df_concat = pd.concat([df_concat, df_group])
print(df_concat.sort_index())
The returned DataFrame (i.e. df_concat) is displayed below. The ordering of RAW and the associated ADC Type values must remain unchanged. I need the return DataFrame to look just like the DataFrame below.
RAW ADC_TYPE_3 ADC_TYPE_7 ADC_TYPE_8 ADC_TYPE_9
0 4.000076e+09 3.0 NaN NaN NaN
1 5.354368e+06 NaN 7.0 NaN NaN
2 4.641792e+06 NaN NaN 8.0 NaN
3 4.289861e+09 NaN NaN NaN 9.0
4 4.136387e+09 3.0 NaN NaN NaN
5 5.440384e+06 NaN 7.0 NaN NaN
6 4.772864e+06 NaN NaN 8.0 NaN
7 4.289881e+09 NaN NaN NaN 9.0

This is just a pivot table with a prefix.
Edit: To preserve sorting, you can reindex from the original dataframe
df = pd.DataFrame({'RAW': {0: 4000076160,
1: 5354368,
2: 4641792,
3: 4289860736,
4: 4136386944,
5: 5440384,
6: 4772864,
7: 4289881216},
'ADC_TYPE': {0: 3, 1: 7, 2: 8, 3: 9, 4: 3, 5: 7, 6: 8, 7: 9}})
out = df.pivot(index='RAW', columns = 'ADC_TYPE', values='ADC_TYPE').add_prefix('ACC_TYPE_').reset_index().rename_axis(None, axis=1)
out = out.set_index('RAW').reindex(df['RAW']).reset_index()
Output
RAW ACC_TYPE_3 ACC_TYPE_7 ACC_TYPE_8 ACC_TYPE_9
0 4000076160 3.0 NaN NaN NaN
1 5354368 NaN 7.0 NaN NaN
2 4641792 NaN NaN 8.0 NaN
3 4289860736 NaN NaN NaN 9.0
4 4136386944 3.0 NaN NaN NaN
5 5440384 NaN 7.0 NaN NaN
6 4772864 NaN NaN 8.0 NaN
7 4289881216 NaN NaN NaN 9.0

Here is a way using str.get_dummies()
df2 = df.set_index('RAW')['ADC_TYPE'].astype(str).str.get_dummies()
(df2.mul(pd.to_numeric(df2.columns),axis=1)
.mask(lambda x: x.eq(0))
.rename('ADC_TYPE_{}'.format,axis=1)
.reset_index())
Here is a slightly different way using pd.get_dummies()
df2 = pd.get_dummies(df.set_index('RAW'),columns = ['ADC_TYPE'])
df2.mul((df2.columns.str.split('_').str[-1]).astype(int)).where(lambda x: x.ne(0))
You can also use set_index() and unstack()
(df.set_index(['RAW',df['ADC_TYPE'].astype(str).map('ADC_TYPE_{}'.format)])['ADC_TYPE']
.unstack().reindex(df['RAW']).reset_index())
Output:
RAW ADC_TYPE_3 ADC_TYPE_7 ADC_TYPE_8 ADC_TYPE_9
0 4000076160 3.0 NaN NaN NaN
1 5354368 NaN 7.0 NaN NaN
2 4641792 NaN NaN 8.0 NaN
3 4289860736 NaN NaN NaN 9.0
4 4136386944 3.0 NaN NaN NaN
5 5440384 NaN 7.0 NaN NaN
6 4772864 NaN NaN 8.0 NaN
7 4289881216 NaN NaN NaN 9.0

I liked the idea of using get_dummies, so I modified it a bit:
df = (pd.get_dummies(df, 'ADC_TYPE', '_', columns=['ADC_TYPE'])
.replace(1, np.nan)
.apply(lambda x: x.fillna(df['ADC_TYPE']))
.replace(0, np.nan))
Output:
RAW ADC_TYPE_3 ADC_TYPE_7 ADC_TYPE_8 ADC_TYPE_9
0 4000076160 3.0 NaN NaN NaN
1 5354368 NaN 7.0 NaN NaN
2 4641792 NaN NaN 8.0 NaN
3 4289860736 NaN NaN NaN 9.0
4 4136386944 3.0 NaN NaN NaN
5 5440384 NaN 7.0 NaN NaN
6 4772864 NaN NaN 8.0 NaN
7 4289881216 NaN NaN NaN 9.0

Using crosstab:
out = pd.crosstab(
df["RAW"], df["ADC_TYPE"], values=df["ADC_TYPE"], aggfunc="first"
).rename_axis(None, axis=1)
out.columns = out.columns.map("ADC_TYPE_{}".format)
out = out.reindex(df["RAW"]).reset_index()
print(out):
RAW ADC_TYPE_3 ADC_TYPE_7 ADC_TYPE_8 ADC_TYPE_9
0 4000076160 3.0 NaN NaN NaN
1 5354368 NaN 7.0 NaN NaN
2 4641792 NaN NaN 8.0 NaN
3 4289860736 NaN NaN NaN 9.0
4 4136386944 3.0 NaN NaN NaN
5 5440384 NaN 7.0 NaN NaN
6 4772864 NaN NaN 8.0 NaN
7 4289881216 NaN NaN NaN 9.0

Related

I cant read [''] values which they are empty <class 'numpy.ndarray'\> [duplicate]

This question already has answers here:
Filter out rows with more than certain number of NaN
(3 answers)
Closed 4 years ago.
I am trying to remove the rows in the data frame with more than 7 null values. Please suggest something that is efficient to achieve this.
If I understand correctly, you need to remove rows only if total nan's in a row is more than 7:
df = df[df.isnull().sum(axis=1) < 7]
This will keep only rows which have nan's less than 7 in the dataframe, and will remove all having nan's > 7.
dropna has a thresh argument. Subtract your desired number from the number of columns.
thresh : int, optional Require that many non-NA values.
df.dropna(thresh=df.shape[1]-7, axis=0)
Sample Data:
print(df)
0 1 2 3 4 5 6 7
0 NaN NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN 5.0
2 6.0 7.0 8.0 9.0 NaN NaN NaN NaN
3 NaN NaN 11.0 12.0 13.0 14.0 15.0 16.0
df.dropna(thresh=df.shape[1]-7, axis=0)
0 1 2 3 4 5 6 7
1 NaN NaN NaN NaN NaN NaN NaN 5.0
2 6.0 7.0 8.0 9.0 NaN NaN NaN NaN
3 NaN NaN 11.0 12.0 13.0 14.0 15.0 16.0

Is there a way for inserting/adding NaN rows and columns on a DataFrame?

I want to turn a DataFrame (or a numpy array):
df1:
0 1 2
0 1. 5. 9.
1 2. 6. 10.
2 3. 7. 11.
3 4. 8. 12.
into a DataFrame like this:
df1
0 1 2 3 4 5 6
0 NaN NaN NaN NaN NaN NaN NaN
1 NaN 1. NaN 5. NaN 9. NaN
2 NaN NaN NaN NaN NaN NaN NaN
3 NaN 2. NaN 6. NaN 10. NaN
4 NaN NaN NaN NaN NaN NaN NaN
5 NaN 3. NaN 7. NaN 11. NaN
6 NaN NaN NaN NaN NaN NaN NaN
7 NaN 4. NaN 8. NaN 12. NaN
8 NaN NaN NaN NaN NaN NaN NaN
, i.e., I want to insert NaN rows and columns on df1 (as many as I want)
Could you make this work even for a large DataFrame, where you cannot do this manually?
So far, I have this:
import numpy as np
import pandas as pd
p = np.arange(1,13).reshape(4,3)
p1 = pd.DataFrame(p)
#Add a row of NaN's on p1
p1.index = range(1, 2*len(p1)+1, 2)
p1 = p1.reindex(index=range(2*len(p1)))
#Repeat for rows...I know its a lil bit st*pid
p1 = pd.DataFrame(p1)
p1.index = range(1, 2*len(p1)+1, 2)
p1 = p1.reindex(index=range(2*len(p1)))
#etc...
p1 = pd.DataFrame(p1)
p1.index = range(1, 2*len(p1)+1, 2)
p1 = p1.reindex(index=range(2*len(p1)))
It seems to work, but only for rows until now...
e.g., see this
Based on this answer you can interleave two dataframes on a particular axis.
pd.concat([df1, df2]).sort_index().reset_index(drop=True)
You can start by interleaving by rows (axis=0) df1 with a dataframe containing nan values. And do the same on the columns (axis=1) with another dataframe of nan values.
df1 = pd.DataFrame([[1., 5., 9.], [2., 6., 10.], [3., 7., 11.], [4., 8., 12.]])
rows, cols = df1.shape
Tricky part is getting the sizes right:
nan1 = pd.DataFrame([[np.nan]*cols]*(rows+1))
nan2 = pd.DataFrame([[np.nan]*(cols + 1)]*(2*rows + 1))
Then perform two consecutives concatenations, on axis=0 (default one) and axis=1:
df2_r = pd.concat([nan1, df1]).sort_index().reset_index(drop=True)
df2 = pd.concat([nan2, df2_r], axis=1).sort_index(axis=1).T.reset_index(drop=True).T
Edit: it seems there's is no built-in method to reset the columns indexing. However this will do:
df.T.reset_index(drop=True).T
Here are the results for each operation:
df1
0 1 2
0 1.0 5.0 9.0
1 2.0 6.0 10.0
2 3.0 7.0 11.0
3 4.0 8.0 12.0
nan1
0 1 2
0 NaN NaN NaN
1 NaN NaN NaN
2 NaN NaN NaN
3 NaN NaN NaN
4 NaN NaN NaN
concat on axis=0
0 1 2
0 NaN NaN NaN
1 1.0 5.0 9.0
2 NaN NaN NaN
3 2.0 6.0 10.0
4 NaN NaN NaN
5 3.0 7.0 11.0
6 NaN NaN NaN
7 4.0 8.0 12.0
8 NaN NaN NaN
nan2
0 1 2 3
0 NaN NaN NaN NaN
1 NaN NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN NaN NaN NaN
4 NaN NaN NaN NaN
5 NaN NaN NaN NaN
6 NaN NaN NaN NaN
7 NaN NaN NaN NaN
8 NaN NaN NaN NaN
concat on axis=1
0 1 2 3 4 5 6
0 NaN NaN NaN NaN NaN NaN NaN
1 NaN 1.0 NaN 5.0 NaN 9.0 NaN
2 NaN NaN NaN NaN NaN NaN NaN
3 NaN 2.0 NaN 6.0 NaN 10.0 NaN
4 NaN NaN NaN NaN NaN NaN NaN
5 NaN 3.0 NaN 7.0 NaN 11.0 NaN
6 NaN NaN NaN NaN NaN NaN NaN
7 NaN 4.0 NaN 8.0 NaN 12.0 NaN
8 NaN NaN NaN NaN NaN NaN NaN
I am curious to see what you have tried so far, but here is an easy "quick and dirty" way to do it for your example. This is not a definitive answer: I'll let you figure out how to generalize it to any dataframe sizes/content you might have.
I am providing this code for your example so you have an idea which pandas functions/properties to use.
import pandas as pd
import numpy as np
# Making your base DataFrame
df = pd.DataFrame([[1,5,9], [2,6,8], [3,7,4]])
df:
0 1 2
0 1 5 9
1 2 6 8
2 3 7 4
spacing out your columns existing columns numbers and adding filling the left columns numbers with NaN:
df.columns = [1,3,5]
for i in range(0, 8, 2):
df[i] = np.NaN
df:
1 3 5 0 2 4 6
0 1 5 9 NaN NaN NaN NaN
1 2 6 8 NaN NaN NaN NaN
2 3 7 4 NaN NaN NaN NaN
Now adding extra rows, with NaN data (we need 4 more with 7 columns)
df2 = pd.DataFrame([[np.NaN] * 7] * 4)
df = pd.concat([df, df2])
df3:
0 1 2 3 4 5 6
0 NaN 1.0 NaN 5.0 NaN 9.0 NaN
1 NaN 2.0 NaN 6.0 NaN 8.0 NaN
2 NaN 3.0 NaN 7.0 NaN 4.0 NaN
0 NaN NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN NaN NaN NaN NaN
3 NaN NaN NaN NaN NaN NaN NaN
As you can see: we have the right data, and it is now only a matter of ordering your rows.
df3.index = [1,3,5,0,2,4,6]
df3 = df3.sort_index()
df3:
0 1 2 3 4 5 6
0 NaN NaN NaN NaN NaN NaN NaN
1 NaN 1.0 NaN 5.0 NaN 9.0 NaN
2 NaN NaN NaN NaN NaN NaN NaN
3 NaN 2.0 NaN 6.0 NaN 8.0 NaN
4 NaN NaN NaN NaN NaN NaN NaN
5 NaN 3.0 NaN 7.0 NaN 4.0 NaN
6 NaN NaN NaN NaN NaN NaN NaN
I think this is a very elegant way to solve this.
array=np.array([[1,5,9],[2,6,10],[3,7,11],[4,8,12]])
Data=pd.DataFrame(array)
Data.index=Data.index*2+1
Data.columns=Data.columns*2+1
Data=Data.reindex(list(range(0,9)))
Data=Data.T.reindex(list(range(0,9)))
A fast way using numpy (work with dataframe as well):
# Sample data
a = np.arange(1,13).reshape(4,3)
df = pd.DataFrame(a)
# New data with empty values
a2 = np.empty([i*2+1 for i in a.shape])
a2[:] = np.nan
a2[1::2, 1::2] = a
Output of pd.DataFrame(a2):
0 1 2 3 4 5 6
0 NaN NaN NaN NaN NaN NaN NaN
1 NaN 1.0 NaN 2.0 NaN 3.0 NaN
2 NaN NaN NaN NaN NaN NaN NaN
3 NaN 4.0 NaN 5.0 NaN 6.0 NaN
4 NaN NaN NaN NaN NaN NaN NaN
5 NaN 7.0 NaN 8.0 NaN 9.0 NaN
6 NaN NaN NaN NaN NaN NaN NaN
7 NaN 10.0 NaN 11.0 NaN 12.0 NaN
8 NaN NaN NaN NaN NaN NaN NaN
Note: If you have a DataFrame, just replace a.shape by df.shape, and a by df.values.

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)

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