как объединить текстовый столбец на основе разницы между другими столбцами?

#python-3.x #pandas #dataframe

#python-3.x #pandas #фрейм данных

Вопрос:

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

        top   bottom                      fontname    size       x0       x1                           text
0   62.890   73.890  HNGMRP HelveticaNeueLTStd-Bd  11.000  321.730  520.115  RISK MANAGEMENT AND INTERNAL 
1   76.893   87.893  HNGMRP HelveticaNeueLTStd-Bd  11.000  321.730  376.334                        CONTROL
2  146.897  157.897  HNGMRP HelveticaNeueLTStd-Bd  11.000   76.535  203.662              COMPANY SECRETARY
3  272.913  283.913  HNGMRP HelveticaNeueLTStd-Bd  11.000   76.535  222.593          INDEPENDENT AUDITORS 
4  286.916  297.916  HNGMRP HelveticaNeueLTStd-Bd  11.000   76.535  167.164                   REMUNERATION
  

Я хочу

  1. объедините текст row[i].text и row[i 1].text , если abs(row[i].bottom - row[i 1].top) < row[i].size
  2. заменить joined_text на row[i].text
  3. заменить row[i].bottom на row[i 1].bottom
  4. отбросить row[i 1]

Например:

  • row 0 имеет text : RISK MANAGEMENT AND INTERNAL
  • row 1 имеет text : CONTROL
  • row 0 и row 1 оба имеют одинаковое size значение: 11
  • abs(row[0].bottom - row[1].top) равно 3,003

Потому что 3.003 < 11

  • желаемый row[0].text является RISK MANAGEMENT AND INTERNAL CONTROL
  • желаемый row[0].bottom является 87.893
  • row[1] удаляется из фрейма данных

Для наглядности желаемый результат следующий:

        top   bottom                      fontname    size       x0       x1                           text
0   62.890   87.893  HNGMRP HelveticaNeueLTStd-Bd  11.000  321.730  520.115  RISK MANAGEMENT AND INTERNAL CONTROL
1  146.897  157.897  HNGMRP HelveticaNeueLTStd-Bd  11.000   76.535  203.662  COMPANY SECRETARY
2  272.913  297.916  HNGMRP HelveticaNeueLTStd-Bd  11.000   76.535  222.593  INDEPENDENT AUDITORS REMUNERATION
  

Это то, что я пытался:

 def df_section_text(self) -> pd.DataFrame:
    df_title_text = self.df_title_text
    df_next_title_text = self.df_title_text.shift(-1).dropna()
    df_section_text = pd.DataFrame()
    
    for next_title, title in zip(df_next_title_text.itertuples(index=False),  df_title_text.itertuples(index=False)):
        diff_btw_titles = abs(title.bottom - next_title.top)
        
        if diff_btw_titles < title.size:
            title = pd.DataFrame([title]).to_dict()
            title['bottom'][0] = next_title.bottom
            title['text'][0]  = next_title.text
            title = pd.DataFrame.from_dict(title)
        
        df_section_text = df_section_text.append([title])
    
    df_section_text = df_section_text.drop_duplicates(subset=['bottom']).reset_index()
    return df_section_text
  

где self.df_title_text находится проблемный фрейм данных, представленный выше.

Это происходит медленно, когда увеличивается номер строки. Есть ли другой более быстрый и элегантный способ получения желаемого результата? Спасибо.

Ответ №1:

Давайте попробуем использовать shift с cumcount , чтобы получить подгруппу, тогда нам просто нужно на основе этого ключа сделать groupby с agg

 s = (df['bottom'].shift()-df['top']).abs().gt(df['size']).cumsum()

out = df.groupby(s).agg({'top':'first',
                         'bottom':'last',
                         'fontname':'first',
                         'size':'first',
                         'x0':'first',
                         'x1':'first',
                         'text':' '.join})


out
Out[20]: 
       top   bottom  ...       x1                               text
0   62.890   87.893  ...  520.115  RISKMANAGEMENTANDINTERNAL CONTROL
1  146.897  157.897  ...  203.662                   COMPANYSECRETARY
2  272.913  297.916  ...  222.593   INDEPENDENTAUDITORS REMUNERATION
[3 rows x 7 columns]
  

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

1. Это отличный ответ. Кроме того, поскольку тип top и bottom является Decimal : s = ((df['bottom'].shift() - df['top']).abs() > df['size']).cumsum() , это позволит избежать ошибки decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>] .