Количество текущих уникальных значений в pandas df

я пытаюсь вернуть count of unique значения pandas df. Это кумулятивный счет в каждом row. Я стремлюсь включить функцию, которая определяет, сколько значений в настоящее время происходит в любой момент времени.

import pandas as pd

df = pd.DataFrame({          
    'A' : ['8:06:00','11:00:00','11:30:00','12:00:00','13:00:00','13:30:00','14:00:00','17:00:00'],
    'B' : ['ABC','ABC','DEF','XYZ','ABC','LMN','DEF','ABC'],          
    'C' : [1,2,1,1,3,1,2,4],            
    })

          A    B  C
0   8:06:00  ABC  1
1  11:00:00  ABC  2
2  11:30:00  DEF  1
3  12:00:00  XYZ  1
4  13:00:00  ABC  3
5  13:30:00  LMN  1
6  14:00:00  DEF  2
7  17:00:00  ABC  4

Итак, есть 4 unique значения col['B']. Который я измеряю через

df1 = df['B'].nunique()

но я надеюсь включить функцию iterates до column чтобы определить, если какое-либо конкретное значение происходит снова. Если не хочу граф к снижению. Если это первый раз, когда появляется значение, я хочу увеличить количество. Если значение уже появилось и появляется снова, счетчик должен оставаться неизменным. Это покажет, сколько значений происходит в любой момент времени.

используя код @jpp, мы производим следующее:

cum_maxer = pd.Series(pd.factorize(df['B'])[0] + 1).cummax()
df['res'] = cum_maxer - df['B'].duplicated().cumsum()

print(df)

Out:

          A    B  C  res
0   8:06:00  ABC  1    1
1  11:00:00  ABC  2    0
2  11:30:00  DEF  1    1
3  12:00:00  XYZ  1    2
4  13:00:00  ABC  3    1
5  13:30:00  LMN  1    2
6  14:00:00  DEF  2    1
7  17:00:00  ABC  4    0

предназначен для вывода 'res'

0  1
1  1
2  2
3  3
4  2
5  3
6  2
7  1

по сути, если value появляется в первый раз I хотите добавить его в cumulative count. Если значение заканчивается (не появляется позже), то количество должно уменьшиться. Если значение уже появилось и появляется снова, счетчик должен оставаться неизменным.

краткое описание каждой строки и предполагаемого вывода:

1st row, ABC появляется в первый раз и появляется позже. Count = +1

2nd row, ABC появляется снова, поэтому нет увеличения. Он также появляется позже, поэтому не уменьшается. Count = no change

3rd row, DEF появляется в первый раз и появляется позже. Count = +1

4th row, XYZ появляется в первый раз, но не появляется позже. На данный момент времени, хотя, 3 значения происходят так count is 3. Счетчик автоматически падает в следующей строке как XYZ has finished

5th row, как было указано выше XYZ закончил так только ABC и DEF в настоящее время включены. The ABC значение также появляется снова так the count is 2.

6th row, LMN появляется в первый раз, поэтому количество увеличивается. Это значит ABC, DEF, LMN являются текущими в данный момент времени. Подобно row 4, LMN не появляется снова, поэтому количество будет уменьшаться в следующей строке как LMN закончил. Count is 3

7-й ряд, DEF и ABC в настоящее время находятся на так count is 2. As DEF не появляется снова, количество будет уменьшаться в следующей строке.

8-й строке, ABC в только значение в настоящее время на so count is 1.

3 ответов


вы также можете использовать np.unique

u = np.unique(df.B, return_index=True)
df['id'] = df.B.map(dict(zip(*u))) + 1

0    1
1    2
2    3
3    1
4    2
5    1

Редактировать Вопрос

для вашего отредактированного вопроса, вот решение. Во-первых, используйте cumcount в перевернутом фрейме данных в видеть будущее

df['u'] = df[::-1].groupby('B').B.cumcount()

такое, что u говорит, сколько раз для каждого B, current B появится в будущем. Затем:zip B и u С вашей логикой, используя S_n = S_{n-1} + new_value + dec здесь new_value флаг True если тока val новое значение, и dec is True если предыдущая строка была последней встречаемости этого значения (т. е. u==0 в то время). Код будет что-то вроде

ids = [1]
seen = set([df.iloc[0].B])
dec = False
for val, u in zip(df.B[1:], df.u[1:]):
    ids.append(ids[-1] + (val not in seen) - dec)
    seen.add(val)
    dec = u == 0

df['S'] = ids

    A           B   C   u   S   expected
0   8:06:00     ABC 1   3   1          1
1   11:00:00    ABC 2   2   1          1
2   11:30:00    DEF 1   1   2          2
3   12:00:00    XYZ 1   0   3          3
4   13:00:00    ABC 3   1   2          2
5   13:30:00    LMN 1   0   3          3
6   14:00:00    DEF 2   0   2          2
7   17:00:00    ABC 4   0   1          1

здесь

>>> (df.S == df.expected).all()
True

тайминги

df = pd.DataFrame({          
'A' : ['8:06:00','11:00:00','11:30:00','12:00:00','13:00:00','13:30:00','14:00:00','17:00:00'],
'B' : ['ABC','ABC','DEF','XYZ','ABC','LMN','DEF','ABC'],          
'C' : [1,2,1,1,3,1,2,4],            
})

def matt(df):
    valsets = df['B'].apply(lambda x: {x})
    union_sets = np.frompyfunc(lambda x, y: x | y, 2, 1)
    intersect_count = np.frompyfunc(lambda x, y: len(x & y), 2, 1)

    seen = union_sets.accumulate(valsets, dtype=np.object)
    to_be_seen = union_sets.accumulate(valsets[::-1], dtype=np.object)[::-1]
    df['res'] = intersect_count(seen, to_be_seen)
    return df

def raf(df):
    ids = [1]
    seen = set([df.iloc[0].B])
    dec = False
    df['u'] = df[::-1].groupby('B').B.cumcount()
    for val, u in zip(df.B[1:], df.u[1:]):
        ids.append(ids[-1] + (val not in seen) - dec)
        seen.add(val)
        dec = u == 0

    df['S'] = ids
    return df

df = pd.concat([df]*10000).reset_index()

результаты

%timeit matt(df)
168 ms ± 12.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit raf(df)
64.2 ms ± 2.04 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

обновление с более быстрым ответом

желаю я заметил @RafaelC это groupby.cumcount() техника, прежде чем я дал ответ ниже. Это навело меня на мысль о более быстром методе. Как заметил @RafaelC, нет необходимости работать с полным списком наблюдений, когда вы работаете через строки; достаточно просто знать, сколько раз текущий символ появляется раньше или позже. В самом деле, как вы отметили в своем обновлении, все, что вам действительно нужно знать, является ли символ в текущей строке только что появился в первый раз (добавить 1 к счету) и появился ли символ в предыдущей строке только в последний раз (вычесть 1 из числа). Имея это в виду, вы можете использовать этот довольно простой и обтекаемый код:

импорт numpy как np, панды как pd

import numpy as np, pandas as pd

df = pd.DataFrame({          
    'A' : ['8:06:00','11:00:00','11:30:00','12:00:00','13:00:00','13:30:00','14:00:00','17:00:00'],
    'B' : ['ABC','ABC','DEF','XYZ','ABC','LMN','DEF','ABC'],          
    'C' : [1,2,1,1,3,1,2,4],            
})

groups = df.groupby('B')['B']
# flag the first and last appearance of each symbol
first_appearance = (groups.cumcount() == 0).astype(int)
last_appearance = (groups.cumcount(False) == 0).astype(int)
# delay effect of last_appearance by one step
last_appearance = pd.np.concatenate(([0], last_appearance.values[:-1]))
df['res'] = (first_appearance - last_appearance).cumsum()
print df
#           A    B  C  res
# 0   8:06:00  ABC  1    1
# 1  11:00:00  ABC  2    1
# 2  11:30:00  DEF  1    2
# 3  12:00:00  XYZ  1    3
# 4  13:00:00  ABC  3    2
# 5  13:30:00  LMN  1    3
# 6  14:00:00  DEF  2    2
# 7  17:00:00  ABC  4    1

называя это matthias2 и повторный запуск тестов @RafaelC дает следующие результаты:

%timeit matthias1(df)
10 loops, best of 3: 109 ms per loop
%timeit raf(df)
1 loops, best of 3: 230 ms per loop
%timeit matthias2(df)
100 loops, best of 3: 7 ms per loop

оригинальный ответ, относительно медленно

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

у панд нет обобщенного


можно использовать pd.factorize чтобы выделить целочисленный идентификатор для каждого уникального значения, используйте cummax о результате для скользящего счета.

df['id'] = pd.factorize(df['B'])[0] + 1
df['count'] = df['id'].cummax()

print(df)

          A    B  C  id  count
0   8:06:00  ABC  1   1      1
1  11:00:00  DEF  1   2      2
2  12:00:00  XYZ  1   3      3
3  13:00:00  ABC  2   1      3
4  13:30:00  LMN  1   4      4
5  14:00:00  DEF  2   2      4
6  17:00:00  ABC  3   1      4

обновление

для вашего желаемого выхода вы можете рассчитать cummax как и раньше и вычесть общее количество повторений:

cum_maxer = pd.Series(pd.factorize(df['B'])[0] + 1).cummax()
df['res'] = cum_maxer - df['B'].duplicated().cumsum()

print(df)

          A    B  C  res
0   8:06:00  ABC  1    1
1  11:00:00  DEF  1    2
2  12:00:00  XYZ  1    3
3  13:00:00  ABC  2    2
4  13:30:00  LMN  1    3
5  14:00:00  DEF  2    2
6  17:00:00  ABC  3    1