Количество текущих уникальных значений в 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