Фрейм данных Pandas — система нумерации в сборе и подсборке без цикла

#python #pandas

#python #pandas

Вопрос:

Я работаю с Pandas, и у меня есть большой список деталей с основным узлом, вспомогательным узлом I, вспомогательным узлом II и вспомогательным узлом III. Только один столбец «Assy» на строку может быть заполнен строкой в фрейме данных. Цель состоит в том, чтобы перенести расположение деталей в систему нумерации.В следующей таблице показан ожидаемый результат:

 Main Assy   Sub Assy I  Sub Assy II Sub Assy III    Level I Level II    Level III   Level IV
asd                                                    1        0            0         0
               fgd                                     1        1            0         0
                           sdd                         1        1            1         0
                           dsd                         1        1            2         0
                           fhg                         1        1            3         0
                                        tdc            1        1            3         1
                                        dyx            1        1            3         2
                                        dsg            1        1            3         3
               dfg                                     1        2            0         0
                           cvf                         1        2            1         0
                           ngs                         1        2            2         0
                           vbn                         1        2            3         0
                                        dsd            1        2            3         1
                                        vcd            1        2            3         2
                                        cbn            1        2            3         3
ged                                                    2        0            0         0
               dfs                                     2        1            0         0
                           aef                         2        1            1         0

  

Мой план состоял в том, чтобы накапливать по строкам в столбцах «Level» до тех пор, пока на более высоком уровне нет изменений. При изменении, таким образом, нового номера на более высоком уровне, выбранная ячейка на более низком уровне должна вернуться к нулю. Нет ли изменений, он сохраняет тот же номер. Я попробовал следующее:

 
df[lambda df: df.columns[0:4]] = df[lambda df: df.columns[0:4]].isna()

for index in range(0,4):
    mask = ((df.iloc[:,index] == False))
    print(mask)
    df.iloc[:,(index 4)] = mask.groupby((~mask).cumsum()).cumsum().astype(int)

  

Поэтому я проверяю, заполнена ли ячейка, путем поиска пропущенных значений. Я не хочу использовать цикл с большим количеством условий для каждой строки из-за большого фрейма данных. Я использовал только этот цикл FOR для столбцов и попытался кумулировать, создав маску, которая показывает изменения с FALSE на TRUE.

Фактический результат:

 Main Assy   Sub Assy I  Sub Assy II Sub Assy III    Level I Level II    Level III   Level IV
asd                                                    1        0            0         0
               fgd                                     0        1            0         0
                           sdd                         0        0            1         0
                           dsd                         0        0            2         0
                           fhg                         0        0            3         0
                                        tdc            0        0            0         1
                                        dyx            0        0            0         2
                                        dsg            0        0            0         3
               dfg                                     0        2            0         0
                           cvf                         0        0            1         0
                           ngs                         0        0            2         0
                           vbn                         0        0            3         0
                                        dsd            0        0            0         1
                                        vcd            0        0            0         2
                                        cbn            0        0            0         3
ged                                                    2        0            0         0
               dfs                                     0        1            0         0
                           aef                         0        0            1         0
  

Каков был бы правильный способ настройки упомянутого условного подсчета без использования циклов?

Ответ №1:

Клавиша

Изменение выходных данных, которое будет применяться к каждой строке, может быть полностью определено текущим «уровнем» и предыдущим уровнем. Здесь «уровень» означает номер индекса столбца, имеющего ненулевую запись.

Другими словами, переменной состояния, сохраняющей уровень предыдущей строки, достаточно для правильного заполнения текущей строки.

Код

 # the working dataset
df2 = df.iloc[:, :4].reset_index(drop=True)  # make a copy
df2.columns = range(4)  # rename columns to (0,1,2,3) for convenience

# output container
arr = np.zeros(df2.shape, dtype=int) 

# state variable: level of the last row
last_lv = 0

for idx, row in df2.iterrows():

    # get current indentation level
    lv = row.first_valid_index()

    if idx > 0:

        # case 1: same or decreased level
        if lv <= last_lv:
            # keep previous levels except current level
            arr[idx, :lv] = arr[idx-1, :lv]
            # current level  
            arr[idx, lv] = arr[idx-1, lv]   1

        # case 2: increased level
        elif lv > last_lv:
            # keep previous levels
            arr[idx, :last_lv 1] = arr[idx - 1, :last_lv 1]
            # start counting the new levels
            arr[idx, last_lv 1:lv 1] = 1  

    # the first row
    else:
        arr[0, 0] = 1

    # update state variable for next use
    last_lv = lv

# append result to dataframe
df[["Level I", "Level II", "Level III", "Level IV"]] = arr
  

Результат

 print(df[["Level I", "Level II", "Level III", "Level IV"]])

    Level I  Level II  Level III  Level IV
0         1         0          0         0
1         1         1          0         0
2         1         1          1         0
3         1         1          2         0
4         1         1          3         0
5         1         1          3         1
6         1         1          3         2
7         1         1          3         3
8         1         2          0         0
9         1         2          1         0
10        1         2          2         0
11        1         2          3         0
12        1         2          3         1
13        1         2          3         2
14        1         2          3         3
15        2         0          0         0
16        2         1          0         0
17        2         1          1         0
  

Примечания

  1. Код просто демонстрирует, как выглядит логика при переходе по каждой строке. Она не совсем оптимизирована, поэтому рассмотрите возможность использования более эффективных представлений данных (например, массива numpy или просто списка номеров уровней), когда эффективность становится проблемой.
  2. Я исследовал библиотеки для tree структур данных, таких как anytree и treelib, в надежде найти автоматизированный способ автоматического вывода иерархии дерева. К сожалению, функции ввода-вывода, подходящие для чтения текстовых файлов с отступом или сопоставимых форматов, похоже, отсутствовали. Это основная причина, по которой я все равно решаю изобрести велосипед.

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

1. Привет @Bill Huang, спасибо за подробное объяснение. К сожалению, я также не нашел лучшего способа решить ее без циклов. Тем не менее, ваше решение более элегантное, чем я мог бы решить в любом случае. Большое вам спасибо. Это работает.