Reputation: 6779
I have a matrix on the following form (not necessarily square):
A B C D
A 0 0.2 0.3 0.5
E 0.2 0.6 0.9 0.2
D 0.5 0.3 0.6 0
F 0.1 0.4 0.5 0.3
And I would like to turn it into a square matrix as follows
A B C D E F
A 0 0.2 0.3 0.5 0.2 0.1
B 0.2 0 0 0.3 0.6 0.4
C 0.3 0 0 0.6 0.9 0.5
D 0.5 0.3 0.6 0 0.2 0.3
E 0.2 0.6 0.9 0.2 0 0
F 0.1 0.4 0.5 0.3 0 0
In other words, I would like to expand both rows and columns so that it is a symmetric square matrix (rows and columns are in the same order) and missing values are filled with 0.
I guessed there should be a way to do this easily/efficiently using built in functions of pandas but I am not familiar with the package.
for convenience:
df = pd.DataFrame([[0, 0.2, 0.3, 0.5],
[0.2, 0.6, 0.9, 0.2],
[0.5, 0.3, 0.6, 0],
[0.1, 0.4, 0.5, 0.3]],
index=['A', 'E', 'D', 'F'],
columns=['A', 'B', 'C', 'D'])
Upvotes: 2
Views: 3133
Reputation: 6779
# create three groups
common = set.intersection(set(df.columns.values), set(df.index))
missing_row = set(df.index) - common
missing_col = set(df.columns.values) - common
# put groups in order (might not be necessary)
ordered = list(common)+list(missing_col)+list(missing_row)
def symmetrize(a):
return a + a.T
# take the common part and extend it to order
common_part = df.loc[common, common]
common_part = common_part.reindex(index=ordered, columns=ordered, fill_value=0)
# take the remaining part
to_add = df.copy().reindex(index=ordered, columns=ordered, fill_value=0) - common_part
# make sure its symmetric, we can do a+a.T because here every value is only written once and its symmetric position is 0
to_add = symmetrize(to_add.values)
# convert to final form
common_part = common_part+common_part.T
result = pd.DataFrame(common_part + to_add, columns=ordered, index=ordered)
result = result[[*ordered]]
result = result.reindex(ordered)
Upvotes: 1
Reputation: 10006
Just as you thought you can definitely do this pretty concisely in Pandas.
One way is by using the very nice combine_first method.
result = df.combine_first(df.T).fillna(0.0)
However, in my testing using timeit that clocked in at 3.62 ms ± 29.2 µs per loop which was actually slightly slower than the time I got for your method (3.5 ms ± 28.6 µs per loop).
However, by calculating this more directly in Pandas using the update method I was able to get this down to 2.04 ms ± 17.2 µs per loop µs per loop (~1.7x as fast).
# Find the combination of both indices
full_index = df.index.union(df.columns)
# Resize the DataFrame to include all the rows and columns
all_data = df.reindex(labels=full_index, axis=0).reindex(labels=full_index, axis=1)
# Update any values we have from the transpose
all_data.update(all_data.T)
# Fill the missing entries
result = all_data.fillna(0.0)
Honestly I wasn't able to get as much of a performance improvement as I thought I might, but both pandas based versions are a little more readable to me at least.
Upvotes: 5