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

Dec 12 2020

У меня есть следующий pd.DataFrame:

source = pd.DataFrame([[0.99, 0.98, 0.93, 0.81, 0.85, 0.71, 0.7, 0.69, 0.68, 0.66], 
              [100, 12, 312, 23, 2, 12, 32, 21, 21, 21]]).T

Я хотел бы как можно быстрее преобразовать его в:

desired_result = pd.DataFrame([[0.9, 0.8, 0.7, 0.6], [424, 25, 44, 63]]).T

Где выше, я определяю интервал, 0.1который я применяю к столбцу 0к sourceфрейму данных и суммирую 1столбец того же фрейма данных. По идее, это должно работать с разными интервалами.

Что пробовал:

  1. Я думал об использовании, pd.cutно, похоже, это не то, что я ищу.

  2. Я знаю, что если я добавлю новый столбец sourceс повторяющимися значениями 0,9, 0,8, 0,7 и 0,6 в соответствующих строках, тогда я смогу использовать groupbyэтот новый столбец, а затем sum, но мне интересно, есть ли более чистый и быстрый способ сделать это? например, что-то вроде этого:

interval = 0.1
source['ints'] = (source[0] / interval).astype(int)
result = source.groupby(source['ints']).sum().reset_index()
result

Однако приведенное выше не сработает, если я, например, изменил интервал с 0,1 на 0,05.

Любая помощь будет оценена.

Ответы

3 PierreD Dec 12 2020 at 21:28

Для скорости: всегда старайтесь векторизовать все, что вы можете, и избегайте apply как можно большего.

Вот более быстрый способ (заслуга @DavidErickson sort=False):

interval = 0.1
source.groupby(np.trunc(source[0] / interval) * interval, sort=False)[1].sum().reset_index()
# out:
     0      1
0  0.9  424.0
1  0.8   25.0
2  0.7   12.0
3  0.6   95.0

Разница в скорости может быть довольно значительной для больших df.

Попробуйте использовать 1 миллион строк, сгруппированных в 10 КБ:

source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))

%%timeit
# ... (as above)
26.7 ms ± 292 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

С applyвместо этого:

1.51 s ± 11 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

(В 50 раз медленнее).

1 DavidErickson Dec 12 2020 at 20:55

Вы можете использовать custom_roundфункцию, которую я сделал 3 модификации изhttps://stackoverflow.com/a/40372261/6366770:

  1. Я использовал np.floorвместо того round, чтобы спуститься как хочешь.
  2. Это испортит значения, которые находятся на «границе» бункера, поэтому я добавляю + base/100(так 0.9было бы 0.9 + .009 = 0.909и округлял до .9 вместо некорректного до 0.8), чтобы оно было чуть выше границы и округлялось правильно. Я думаю, это тебя накроет. Вы можете сделать это 1 / 1000на всякий случай.
  3. Ответ, который я искал int, поэтому удален int, поскольку мы смотрим на округление с плавающей запятой

source = pd.DataFrame(np.random.normal(scale=1000, size=(int(1e6), 2)))

def custom_round(x, y, base):
    return source.groupby((base * np.floor((x + (base / 100)) / base)), sort=False)[y].sum()


%timeit custom_round(source[0], 1, .1)
89.8 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

На моем компьютере принятый ответ медленнее:

102 ms ± 1.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)