How to concatenate near duplicate rows in DataFrame - python

From the original data, there are duplicated data. The duplicates with different DB have to concat to the back of the former one.Is there any way to merge two tables into one as shown below by comparing between data?
From the original data using drop.duplicates and duplicated, i get two tables and wanted to compare them using dictionaries, but by making rows as dictionaries in both the table, the keys are the same in every dictionary which i can't merge them together.
This is the original data given
DB TITLE ISSN IBSN
0 M a 1 NaN
1 M d 1 NaN
2 M c 1 NaN
3 N b 1 NaN
4 N a 1 NaN
5 N d 1 NaN
6 O c 1 NaN
7 O e 1 NaN
8 O a 1 NaN
9 O b 1 NaN
By using drop_duplicates and duplicated:
DB TITLE ISSN IBSN DB TITLE ISSN IBSN
0 M a 1 NaN 0 N a 1 NaN
1 M d 1 NaN 1 N d 1 NaN
2 M c 1 NaN 2 O c 1 NaN
3 N b 1 NaN 3 O a 1 NaN
4 O e 1 NaN 4 O b 1 NaN
This is the kind of dictionary i get from the rows:
{'DB': 'N', 'TITLE': 'a', 'ISSN': 1, 'IBSN': 'NaN'}
{'DB': 'M', 'TITLE': 'a', 'ISSN': 1, 'IBSN': 'NaN'}
I expect the output to be
DB TITLE ISSN IBSN DB TITLE ISSN ISBN DB TITLE ISSN IBSN
0 M a 1.0 NaN N a 1.0 NaN O a 1.0 NaN
1 N b 1.0 NaN O b 1.0 NaN NaN NaN NaN NaN
2 M d 1.0 NaN N d 1.0 NaN NaN NaN NaN NaN
3 M c 1.0 NaN O c 1.0 NaN NaN NaN NaN NaN
4 O e 1.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
The order of 'TITLE' in the column is not important but the DB have to be sorted alphabetically from left to right.

I think the simplest way to do this is using cumcount to segregate sub-groups, then use concat with join='outer':
grps = [
g.set_index('TITLE') for _, g in df.groupby(df.groupby('TITLE').cumcount())
]
pd.concat(grps, join='outer', axis=1, sort=True)
DB ISSN IBSN DB ISSN IBSN DB ISSN IBSN
a M 1 NaN N 1.0 NaN O 1.0 NaN
b N 1 NaN O 1.0 NaN NaN NaN NaN
c M 1 NaN O 1.0 NaN NaN NaN NaN
d M 1 NaN N 1.0 NaN NaN NaN NaN
e O 1 NaN NaN NaN NaN NaN NaN NaN
If you need "TITLE" too, use set_index with drop=False:
grps = [
g.set_index('TITLE', drop=False)
for _, g in df.groupby(df.groupby('TITLE').cumcount())
]
pd.concat(grps, join='outer', axis=1, sort=True)
DB TITLE ISSN IBSN DB TITLE ISSN IBSN DB TITLE ISSN IBSN
a M a 1 NaN N a 1.0 NaN O a 1.0 NaN
b N b 1 NaN O b 1.0 NaN NaN NaN NaN NaN
c M c 1 NaN O c 1.0 NaN NaN NaN NaN NaN
d M d 1 NaN N d 1.0 NaN NaN NaN NaN NaN
e O e 1 NaN NaN NaN NaN NaN NaN NaN NaN NaN

Related

How can I get a df that is exactly like the .txt imput? (file has " " separator)

I have a TXT file that looks like this:
DBSH
(NFr) O (NTo) Nc C (Vmn (Vmx Bctrl (Qini) T A (Extr
121 D 0950 1050 121 -10. C
(G O E (U) UOp (Sht ) M
1 2 2 -5. S
2 1 0 -10. S
FBAN
(NFr) O (NTo) Nc C (Vmn (Vmx Bctrl (Qini) T A (Extr
125 D 0950 1050 125 3.1 C
(G O E (U) UOp (Sht ) M
1 3 1 3.1 S
2 1 0 -5. S
FBAN
I want to get a dataframe that looks like this:
DBSH NaN NaN NaN NaN Nan NaN NaN NaN NaN NaN NaN
(NFr) O (NTo) Nc C (Vmn (Vmx Bctrl (Qini) T A (Extr
121 NaN NaN NaN D 0950 1050 121 -10. C NaN NaN
(G O E (U) UOp (Sht ) M NaN NaN NaN NaN NaN
1 NaN NaN 2 2 -5. S NaN NaN NaN NaN NaN
2 NaN NaN 1 0 -10. S NaN NaN NaN NaN NaN
FBAN NaN NaN NaN NaN Nan NaN NaN NaN NaN NaN NaN
(NFr) O (NTo) Nc C (Vmn (Vmx Bctrl (Qini) T A (Extr
125 NaN NaN NaN D 0950 1050 125 3.1 C NaN NaN
(G O E (U) UOp (Sht ) M NaN NaN NaN NaN NaN
1 NaN NaN 3 1 3.1 S NaN NaN NaN NaN NaN
2 NaN NaN 1 0 -5. S NaN NaN NaN NaN NaN
FBAN NaN NaN NaN NaN Nan NaN NaN NaN NaN NaN NaN
99999 NaN NaN NaN NaN Nan NaN NaN NaN NaN NaN NaN
I've done:
data_file = _teste
data_file_delimiter = " "
largest_columns_count = 0
with open(data_file, 'r') as temp_f:
lines = temp_f.readlines()
for l in lines:
column_count = len(l.split(data_file_delimiter))+1
largest_columns_count = column_count if largest_columns_count < column_count else largest_columns_count
column_names = [i for i in range(0, largest_columns_count - 1)]
df = pd.read_csv(data_file, header=None, delimiter=data_file_delimiter,
names=column_names, dtype=object)
and got an output that is not satisfying. I don't realy know what to do from this, what's an awnser for this?
Once you find the "largest_colummn"(which should be largest row), go through each line and change the delimiter to a comma. Add as many comas until you get the number of correct columns for each line. I am guessing the space delimiter could make things complicated. You can add Nan when adding comas or change the empty responses to NaN at a late point. and replace commas at the end if that is what you want

Get unique values and their occurrence out of one dataframe into a new dataframe using Pandas DataFrame

I want to turn my dataframe with non-distinct values underneath each column header into a dataframe with distinct values underneath each column header with next to it their occurrence in their particular column. An example:
My initial dataframe is visible underneath:
A B C D
0 CEN T2 56
2 DECEN T2 45
3 ONBEK T2 84
NaN CEN T1 59
3 NaN T1 87
NaN NaN T2 NaN
0 NaN NaN 98
NaN CEN NaN 23
NaN CEN T1 65
where A, B, C and D are the column headers with each 9 values underneath it (blanks included).
My preferred output dataframe should look like: (first a column of unique values for each column in the original dataframe and next to it their occurrence in that particular column)
A B C D A B C D
0 CEN T2 56 2 4 4 1
2 DECEN T1 45 1 1 3 1
3 ONBEK NaN 84 2 1 NaN 1
Nan NaN NaN 59 NaN NaN NaN 1
NaN NaN NaN 87 NaN NaN NaN 1
NaN NaN NaN 98 NaN NaN NaN 1
NaN NaN NaN 23 NaN NaN NaN 1
NaN NaN NaN 65 NaN NaN NaN 1
where A, B, C and D are the column headers with underneath them first the distinct values for each column from the original .csv-file and next to it the occurence of each element in their particular column.
Anybody ideas?
The code below is used to get the unique values out of each column into a new dataframe. I tried to do something with .value_counts to get the occurrence in each column but there I failed to get it into one dataframe again with the unique values..
df
new_df=pd.concat([pd.Series(df[i].unique()) for i in df.columns], axis=1)
new_df.columns=df.columns
new_df
The difficult part is keeping values of columns in each row aligned. To do this, you need to construct a new dataframe from unique, and pd.concat on with value_counts map to each column of this new dataframe.
new_df = (pd.DataFrame([df[c].unique() for c in df], index=df.columns).T
.dropna(how='all'))
df_final = pd.concat([new_df, *[new_df[c].map(df[c].value_counts()).rename(f'{c}_Count')
for c in df]], axis=1).reset_index(drop=True)
Out[1580]:
A B C D A_Count B_Count C_Count D_Count
0 0 CEN T2 56 2.0 4.0 4.0 1
1 2 DECEN T1 45 1.0 1.0 3.0 1
2 3 ONBEK NaN 84 2.0 1.0 NaN 1
3 NaN NaN NaN 59 NaN NaN NaN 1
4 NaN NaN NaN 87 NaN NaN NaN 1
5 NaN NaN NaN 98 NaN NaN NaN 1
6 NaN NaN NaN 23 NaN NaN NaN 1
7 NaN NaN NaN 65 NaN NaN NaN 1
If you only need to keep alignment between each pair of column and its count such as A - A_Count, B - B_Count..., it simply just use value_counts with reset_index some commands to change axis names
cols = df.columns.tolist() + (df.columns + '_Count').tolist()
new_df = pd.concat([df[col].value_counts(sort=False).rename_axis(col).reset_index(name=f'{col}_Count')
for col in df], axis=1).reindex(new_cols, axis=1)
Out[1501]:
A B C D A_Count B_Count C_Count D_Count
0 0.0 ONBEK T2 56.0 2.0 1.0 4.0 1
1 2.0 CEN T1 45.0 1.0 4.0 3.0 1
2 3.0 DECEN NaN 84.0 2.0 1.0 NaN 1
3 NaN NaN NaN 59.0 NaN NaN NaN 1
4 NaN NaN NaN 87.0 NaN NaN NaN 1
5 NaN NaN NaN 98.0 NaN NaN NaN 1
6 NaN NaN NaN 23.0 NaN NaN NaN 1
7 NaN NaN NaN 65.0 NaN NaN NaN 1

Copying existing columns between DataFrames

having a DataFrame with e.g. 10 columns (a, b, c...) and another smaller one with just let's say 3 of them (d, f, h), what is the 'best' way to copy the columns from the second DataFrame to the first?
The below seems to do the trick but I'm wondering if I should use join, merge or something else instead (for better performance/cleaner code)?
dfOutput = pd.DataFrame(columns=['a','b','c','d','e','f','g','h','i','j'])
melted = pd.DataFrame([[1,2,3],[4,5,6],[7,8,9]],columns=['d','h','i'])
dfOutput[melted.columns] = melted[melted.columns]
I believe you need df.merge() and df.reindex():
melted.merge(dfOutput,on=['d','h','i'],how='left').reindex(dfOutput.columns,axis=1)
a b c d e f g h i j
0 NaN NaN NaN 1 NaN NaN NaN 2 3 NaN
1 NaN NaN NaN 4 NaN NaN NaN 5 6 NaN
2 NaN NaN NaN 7 NaN NaN NaN 8 9 NaN
you can reassign this to the first dataframe :
dfOutput = melted.merge(dfOutput,on=['d','h','i'],how='left').reindex(dfOutput.columns,axis=1)
Scenario 2 : If you already have data in certain columns , use dfOutput.update(melted) to update the first dataframe with the second:
For example:
dfOutput:
a b c d e f g h i j
0 NaN NaN NaN 1 NaN NaN NaN NaN NaN NaN
1 NaN NaN NaN 2 NaN NaN NaN NaN NaN NaN
2 NaN NaN NaN 3 NaN NaN NaN NaN NaN NaN
melted:
d h i
0 5 6 7
1 4 8 6
2 7 4 9
>>dfOutput.update(melted)
>>dfOutput
a b c d e f g h i j
0 NaN NaN NaN 5 NaN NaN NaN 6 7 NaN
1 NaN NaN NaN 4 NaN NaN NaN 8 6 NaN
2 NaN NaN NaN 7 NaN NaN NaN 4 9 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.

Stack and Pivot Dataframe in Python

I have a wide dataframe that I want to stack and pivot and can't quite figure out how to do it.
Here is what I am starting with
testdf = pd.DataFrame({"Topic":["A","B","B","C","A"],
"Org":[1,1,2,3,5,],
"DE1":["a","c","d","e","f"],
"DE2":["b","c","a","d","h"],
"DE3":["a","c","b","e","f"] })
testdf
Out[40]:
DE1 DE2 DE3 Org Topic
0 a b a 1 A
1 c c c 1 B
2 d a b 2 B
3 e d e 3 C
4 f h f 5 A
What I would like to do is pivot the table so that the column values for Org are the Column names and the column values for each name are the matching values from D1,D2 and D3 and finally have Topic as the index. Is this even possible?
EDIT: As Randy C pointed out, if I use pivot I can get the following;
testdf.pivot(index = "Topic",columns = "Org")
Out[44]:
DE1 DE2 DE3
Org 1 2 3 5 1 2 3 5 1 2 3 5
Topic
A a NaN NaN f b NaN NaN h a NaN NaN f
B c d NaN NaN c a NaN NaN c b NaN NaN
C NaN NaN e NaN NaN NaN d NaN NaN NaN e NaN
Which is close, but I would like to have it so that the DE values are "stacked" and not wide. The result would look like;
Org 1 2 3 5
Topic
A a NaN NaN f
A b NaN NaN h
A a NaN NaN f
B c d NaN NaN
B c a NaN NaN
B c b NaN NaN
C NaN NaN e NaN
C NaN NaN d NaN
C NaN NaN e NaN
Perhaps:
In[249]: testdf.pivot("Org","Topic").T
Out[249]:
Org 1 2 3 5
Topic
DE1 A a NaN NaN f
B c d NaN NaN
C NaN NaN e NaN
DE2 A b NaN NaN h
B c a NaN NaN
C NaN NaN d NaN
DE3 A a NaN NaN f
B c b NaN NaN
C NaN NaN e NaN
It's not 100% clear to me what your desired output is, but as best I can understand it, .pivot() does seem to be at least close to what you're looking for:
In [8]: testdf.pivot("Topic", "Org")
Out[8]:
DE1 DE2 DE3
Org 1 2 3 5 1 2 3 5 1 2 3 5
Topic
A a NaN NaN f b NaN NaN h a NaN NaN f
B c d NaN NaN c a NaN NaN c b NaN NaN
C NaN NaN e NaN NaN NaN d NaN NaN NaN e NaN

Categories