Это решение переносит данные на numpy территорию, запускает некоторые вычисления с numpy isnan и numpy, сжимает , создает отдельные кадры данных и объединяет их в один кадр данных. с pandas concat :
data = """ 0 1 2 3 4 5 6
3 74 None None None None None
4 2 None None None None None
None None -9 None None None None
None None None -1 2 -16 -21
None None 1 None None None None
None None 28 None None None None """
df = pd.read_csv(StringIO(data), sep='\s{2,}',engine='python', na_values=["None"])
df
0 1 2 3 4 5 6
0 3.0 74.0 NaN NaN NaN NaN NaN
1 4.0 2.0 NaN NaN NaN NaN NaN
2 NaN NaN -9.0 NaN NaN NaN NaN
3 NaN NaN NaN -1.0 2.0 -16.0 -21.0
4 NaN NaN 1.0 NaN NaN NaN NaN
5 NaN NaN 28.0NaN NaN NaN NaN
#convert to numpy array
M = df.to_numpy()
#get True or False depending on the null status of each entry
condition = ~np.isnan(M)
#for each array, get entries that are not null
step1 = [np.compress(ent,arr) for ent,arr in zip(condition,M)]
step1
#concatenate each dataframe
step2 = pd.concat([pd.DataFrame(ent).T for ent in step1],ignore_index=True)
print(step2)
0 1 2 3
0 3.0 74.0 NaN NaN
1 4.0 2.0 NaN NaN
2 -9.0 NaN NaN NaN
3 -1.0 2.0 -16.0 -21.0
4 1.0 NaN NaN NaN
5 28.0 NaN NaN NaN
#alternatively, from step1 we could find the longest array and use that value to resize all the other arrays :
reshape = max(len(arr) for arr in step1)
#this happens in place
[arr.resize(reshape,refcheck=False) for arr in step1]
outcome = pd.DataFrame(step1).where(lambda x: x.ne(0),np.nan)