панды, изменяющие структуру данных со строками разной длины

#python #pandas

#python #панды

Вопрос:

У меня есть следующий фрейм данных pandas с индексом слева:

       0   1   2   3   4   5   6   7   8   9   10   11   12   13   14
A17   a   b   1  AUG) NaN NaN NaN NaN NaN NaN NaN  NaN  NaN  NaN  NaN  
nn6   c   d   2  POS) e   f   2   Hi)
AZV   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN  NaN  NaN  NaN
JFK   a   b   4   UUI) c   v   8   Yo)  t   f   9   po)
 

Я хочу изменить его форму, чтобы:

      0    1    2    3
A17  a    b    1   AUG)
nn6  c    d    2   POS)
nn6  e    f    2   Hi)
AZV  NaN  NaN  NaN NaN
JFK  a    b    4   UUI)
JFK  c    v    8   Yo)
JFK  t    f    9   po
 

Я пробовал reshape() и использовал itertools итерацию по столбцам, но, похоже, до сих пор не могу ее получить.

По сути, каждый раз, когда встречается a), затем переходите на новую строку. Реальная таблица содержит более 150 столбцов.

Спасибо

Комментарии:

1. Всегда ли значения кратны четырем?

Ответ №1:

Другой вариант, который не требует перебора строк (который может быть очень медленным, если их много), — это выполнить следующее

 [ins] In [1]: df
Out[1]: 
     0    1    2     3    4    5    6    7
A17  a    b    1  AUG)  NaN  NaN  NaN  NaN
nn6  c    d    2  POS)    e    f    2  HI)
AVZ     NaN  NaN   NaN  NaN  NaN  NaN  NaN

[ins] In [2]: joined = df.apply(lambda x: ' '.join([str(xi) for xi in x]), axis=1)
[ins] In [4]: split = joined.str.split(')', expand=True).reset_index(drop=False).melt(id_vars='index')

[ins] In [6]: split.drop('variable', axis=1, inplace=True)

[ins] In [7]: split
Out[7]: 
  index                        value
0   A17                    a b 1 AUG
1   nn6                    c d 2 POS
2   AVZ  nan nan nan nan nan nan nan
3   A17              nan nan nan nan
4   nn6                     e f 2 HI
5   AVZ                         None
6   A17                         None
7   nn6                             
8   AVZ                         None

[ins] In [8]: sel = split['value'].str.strip().str.len() > 0

[ins] In [9]: split = split.loc[sel, :]

[ins] In [9]: split
Out[9]: 
  index                        value
0   A17                    a b 1 AUG
1   nn6                    c d 2 POS
2   AVZ  nan nan nan nan nan nan nan
3   A17              nan nan nan nan
4   nn6                     e f 2 HI

[ins] In [10]: out = split['value'].str.strip().str.split(' ', expand=True)

[ins] In [11]: out.index = split['index']

[ins] In [12]: out
Out[12]: 
         0    1    2    3     4     5     6
index                                      
A17      a    b    1  AUG  None  None  None
nn6      c    d    2  POS  None  None  None
AVZ    nan  nan  nan  nan   nan   nan   nan
A17    nan  nan  nan  nan  None  None  None
nn6      e    f    2   HI  None  None  None
 

а затем нужно отбросить с 4-го по 6-й столбец, что очень просто.
Я добавил некоторые выходные данные, чтобы вы могли видеть, что происходит на каждом шаге.

Комментарии:

1. Спасибо, протестировано на df с 10 000 строк и> 800 столбцов. Сработало быстро.

2. Apply перебирает строки, он не использует операции на основе набора. удивлен, что это решение сработало быстро

3. @Manakin, обратите внимание на аргумент оси. Apply работает со столбцами, рассматривая каждый из них как серию. Таким образом, количество циклов определяется количеством столбцов, а не строк.

Ответ №2:

Я думаю, что эффективным способом объединения значений было бы разделить фрейм данных на 4 равные части и повторно объединить его по индексу.

Проблема здесь заключается в именах столбцов, которые мы можем динамически переименовывать внутри оператора concat.

 import numpy as np 
lst = np.array_split([i for i in range(len(df.columns))],4)

[array([0, 1, 2, 3]),
 array([4, 5, 6, 7]),
 array([ 8,  9, 10, 11]),
 array([12, 13, 14])]
 

 dfs = pd.concat( [
        df.iloc[:,i].rename(columns=
                            dict(zip(df.iloc[:,i].columns,range(4)))
                            )
    
        for i in lst
    ]).dropna(how='all')
 

  print(dfs)

 0  1    2     3
A17  a  b  1.0  AUG)
nn6  c  d  2.0  POS)
JFK  a  b  4.0  UUI)
nn6  e  f  2.0   Hi)
JFK  c  v  8.0   Yo)
JFK  t  f  9.0   po)
 

единственная разница здесь в том, что вам не хватает строки из желаемого результата из-за того, что она является na.

мы можем выполнить объединение с combine_first , чтобы получить дельту между двумя фреймами данных.

 dfs = dfs.combine_first(df.iloc[:,:0])

print(dfs)

       0    1    2     3
A17    a    b  1.0  AUG)
AZV  NaN  NaN  NaN   NaN
JFK    a    b  4.0  UUI)
JFK    c    v  8.0   Yo)
JFK    t    f  9.0   po)
nn6    c    d  2.0  POS)
nn6    e    f  2.0   Hi)
 

Ответ №3:

Есть и другие варианты, такие как нарезка столбцов и добавление, но это довольно просто.

 output = []
for index, row in df.iterrows():
    r = row.dropna().values
    if len(r) <= 4:
        output.append([index,*r])
    else:
        for x in np.reshape(r, (int(len(r)/4),4)):
            output.append([index,*x])
            
pd.DataFrame(output).set_index(0)