Создайте новый столбец в фрейме данных на основе сравнения со значениями строк

#python #pandas #dataframe

Вопрос:

У меня есть фрейм данных, который выглядит примерно так:

 df = pd.DataFrame({'a1': [1,2,3,4],
               'des_2': ['a','b','a', 'd'],
               'des_4': ['a','c','c', 'd'],
               'des_1': ['a','b','c', 'd'],
               'des_3': ['a','b','c', 'a'],
               'a2': [1,2,3,4],
               'a3': [1,2,3,4]
               })
 

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

В первой строке все случаи равны a, поэтому количество увеличивается до 4. Во второй строке des_1, des_2 и des_3 равны b, поэтому сумма складывается до 3. И так далее.

    a1  des_2  des_4  des_1  des_3  a2  a3  count
0  1   a      a      a      a      1   1   4
1  2   b      c      b      b      2   2   3
2  3   a      c      c      c      3   3   1
3  4   d      d      d      a      4   4   2
 

У меня есть рабочий код, но я чувствую, что он не очень питонический:

 cols_list = sorted(df.columns.tolist())
cols_list = list(filter(lambda x: 'des_' in x, cols_list))

new_df = df[cols_list]
lists = new_df.values.tolist()
occ_list = []
for lst in lists:
    first_occurrence = lst[0]
    counter = 0
    for occurrence in lst:
        if occurrence == first_occurrence:
            counter  = 1
        else:
            break
    occ_list.append(counter)
    counter = 0

df['count'] = occ_list
 

Есть идеи, как его уменьшить?

Спасибо!

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

1. Почему количество строк 3 (индекс 2) отображается как 1? Есть два соседних значения «c» в des_3 и des_4

2. Привет, not_speshal! Потому что я хочу подсчитать только первый набор соседних окон столбцов des, отсортированных в алфавитном порядке. у des_1 есть «c», но у des_2 есть «a»

3. О, понял тебя. Позвольте мне отредактировать свой ответ

Ответ №1:

Вот способ сделать это:

 df = pd.DataFrame({'a1': [1,2,3,4],
               'des_2': ['a','b','a', 'd'],
               'des_4': ['a','c','c', 'd'],
               'des_1': ['a','b','c', 'd'],
               'des_3': ['a','b','c', 'a'],
               'a2': [1,2,3,4],
               'a3': [1,2,3,4]
               })

df_des = df.filter(like='des_').sort_index(axis=1)
df['count'] = (
               (df_des == df_des.shift(1, axis=1).bfill(axis=1))
                   .cumprod(axis=1)
                   .sum(axis=1)
              )
df
 

Выход:

    a1 des_2 des_4 des_1 des_3  a2  a3  count
0   1     a     a     a     a   1   1      4
1   2     b     c     b     b   2   2      3
2   3     a     c     c     c   3   3      1
3   4     d     d     d     a   4   4      2
 

Объяснение:

  • Во-первых, подмножество фрейма данных с помощью filter like параметра with, чтобы получить только столбцы с «des_» и отсортировать по алфавиту sort_index .
  • Затем сравните со сдвинутым shift axis=1 фреймом данных, используемым bfill для обратной заливки NaN.
  • Используйте cumprod только для подсчета первых последовательных повторов, как только будет найдено другое значение, cumprod сохранит нули.
  • Наконец, sum с axis=1

Ответ №2:

Что такое «питонический», очень субъективно, но вот один из способов, которым вы можете сократить свой код:

 #function to return the longest streak of the first element
def streak(srs):
    groups = (srs!=srs.shift()).cumsum()
    return srs.groupby(groups).size().iat[0]

#keep only the columns you need in the correct order
sample = df[[f"des_{i 1}" for i in range(4)]]
df["count"]= sample.apply(streak, axis=1)

>>> df
   a1 des_2 des_4 des_1 des_3  a2  a3  count
0   1     a     a     a     a   1   1      4
1   2     b     c     b     b   2   2      3
2   3     a     c     c     c   3   3      1
3   4     d     d     d     a   4   4      2