Пошаговая функция векторизации для столбца в pandas кадре данных - PullRequest
0 голосов
/ 17 апреля 2020

У меня есть немного сложная функция, которая назначает уровень качества заданным данным с помощью заранее определенных пошаговых логик c (зависит от фиксированных границ, а также от относительных границ на основе реального значения). Функция get_quality () ниже делает это для каждой строки, и использование pandas DataFrame.apply довольно медленно для огромных наборов данных. Поэтому я хотел бы векторизовать этот расчет. Очевидно, что я мог бы сделать что-то вроде df.groupby(pd.cut(df.ground_truth, [-np.inf, 10.0, 20.0, 50.0, np.inf])) для внутреннего if-logi c, а затем применить аналогичную подгруппу внутри каждой группы (на основе границ каждой группы), но как бы я это сделал для последнего раздела, который зависит от заданного значения real / ground_truth в каждой строке?

Использование df['quality'] = np.vectorize(get_quality)(df['measured'], df['ground_truth']) уже намного быстрее, но существует ли реальный векторизованный способ вычисления того же столбца 'quality'?

import pandas as pd
import numpy as np
from bisect import bisect

quality_levels = ['WayTooLow', 'TooLow', 'OK',  'TooHigh', 'WayTooHigh']

# Note: to make the vertical borders always lead towards the 'better' score we use a small epsilon around them
eps = 0.000001

def get_quality(measured_value, real_value):
    diff = measured_value - real_value
    if real_value <= 10.0:
        i = bisect([-4.0-eps, -2.0-eps, 2.0+eps, 4.0+eps], diff)
        return quality_levels[i]
    elif real_value <= 20.0:
        i = bisect([-14.0-eps, -6.0-eps, 6.0+eps, 14.0+eps], diff)
        return quality_levels[i]
    elif real_value <= 50.0:
        i = bisect([-45.0-eps, -20.0-eps, 20.0+eps, 45.0+eps], diff)
        return quality_levels[i]
    else:
        i = bisect([-0.5*real_value-eps, -0.25*real_value-eps,
                    0.25*real_value+eps, 0.5*real_value+eps], diff)
        return quality_levels[i]

N = 100000
df = pd.DataFrame({'ground_truth': np.random.randint(0, 100, N),
                   'measured': np.random.randint(0, 100, N)})


df['quality'] = df.apply(lambda row: get_quality((row['measured']), (row['ground_truth'])), axis=1)
print(df.head())
print(df.quality2.value_counts())

#   ground_truth  measured     quality
#0            51         1   WayTooLow
#1             7        25  WayTooHigh
#2            38        95  WayTooHigh
#3            76        32   WayTooLow
#4             0        18  WayTooHigh

#OK            30035
#WayTooHigh    24257
#WayTooLow     18998
#TooLow        14593
#TooHigh       12117

1 Ответ

0 голосов
/ 17 апреля 2020

Это возможно с np.select .

import numpy as np

quality_levels = ['WayTooLow', 'TooLow', 'OK',  'TooHigh', 'WayTooHigh']

def get_quality_vectorized(df):
    # Prepare the first 4 conditions, to match the 4 sets of boundaries.
    gt = df['ground_truth']
    conds = [gt <= 10, gt <= 20, gt <= 50, True]
    lo = np.select(conds, [2, 6, 20, 0.25 * gt])
    hi = np.select(conds, [4, 14, 45, 0.5 * gt])

    # Prepare inner 5 conditions, to match the 5 quality levels.
    diff = df['measured'] - df['ground_truth']
    quality_conds = [diff < -hi-eps, diff < -lo-eps, diff < lo+eps, diff < hi+eps, True]
    df['quality'] = np.select(quality_conds, quality_levels)
    return df
...