programing

기존 열에서 새 열을 생성하기 위해 적용되는 판다 성능 대 np.vectorize

subpage 2023. 7. 18. 21:45
반응형

기존 열에서 새 열을 생성하기 위해 적용되는 판다 성능 대 np.vectorize

저는 Pandas 데이터 프레임을 사용하고 있으며 기존 열의 함수로 새 열을 만들고 싶습니다.사이의 속도 차이에 대한 좋은 논의를 보지 못했습니다.df.apply()그리고.np.vectorize()그래서 저는 여기에 물어보려고 했습니다.

apply()기능이 느립니다.제가 측정한 바에 따르면(아래 일부 실험에서 보여짐),np.vectorize() 함수 DataFrame을 빠름apply()적어도 2016년 맥북 프로에서는.이것은 예상된 결과이며, 그 이유는 무엇입니까?

를 들어, 다음과 같은 에 예를들어, 다과같프레있가다정니가 있다고 .N복사:

N = 10
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
df.head()
#     A   B
# 0  78  50
# 1  23  91
# 2  55  62
# 3  82  64
# 4  99  80

두의 열의 가정해 .A그리고.B아래예서는간기사능용니다합을단한제에▁a다▁▁in▁use▁▁illdivide()그 기능을 적용하기 위해, 나는 다음 중 하나를 사용할 수 있습니다.df.apply()또는np.vectorize():

def divide(a, b):
    if b == 0:
        return 0.0
    return float(a)/b

df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)

df['result2'] = np.vectorize(divide)(df['A'], df['B'])

df.head()
#     A   B    result   result2
# 0  78  50  1.560000  1.560000
# 1  23  91  0.252747  0.252747
# 2  55  62  0.887097  0.887097
# 3  82  64  1.281250  1.281250
# 4  99  80  1.237500  1.237500

가 내가늘을 ,N 개로, 는 100만 개 이상의 크기로 합니다.np.vectorize() 빠릅니다.df.apply().

다음은 완전한 벤치마킹 코드입니다.

import pandas as pd
import numpy as np
import time

def divide(a, b):
    if b == 0:
        return 0.0
    return float(a)/b

for N in [1000, 10000, 100000, 1000000, 10000000]:    

    print ''
    A_list = np.random.randint(1, 100, N)
    B_list = np.random.randint(1, 100, N)
    df = pd.DataFrame({'A': A_list, 'B': B_list})

    start_epoch_sec = int(time.time())
    df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
    end_epoch_sec = int(time.time())
    result_apply = end_epoch_sec - start_epoch_sec

    start_epoch_sec = int(time.time())
    df['result2'] = np.vectorize(divide)(df['A'], df['B'])
    end_epoch_sec = int(time.time())
    result_vectorize = end_epoch_sec - start_epoch_sec


    print 'N=%d, df.apply: %d sec, np.vectorize: %d sec' % \
            (N, result_apply, result_vectorize)

    # Make sure results from df.apply and np.vectorize match.
    assert(df['result'].equals(df['result2']))

결과는 다음과 같습니다.

N=1000, df.apply: 0 sec, np.vectorize: 0 sec

N=10000, df.apply: 1 sec, np.vectorize: 0 sec

N=100000, df.apply: 2 sec, np.vectorize: 0 sec

N=1000000, df.apply: 24 sec, np.vectorize: 1 sec

N=10000000, df.apply: 262 sec, np.vectorize: 4 sec

한다면np.vectorize()일반적으로 항상 보다 빠릅니다.df.apply()그렇다면 왜일까요?np.vectorize()더 이상 언급되지 않았습니까?는 "StackOverflow"와 .df.apply()를 들어 다음과 같습니다.

판다는 다른 열의 값을 기반으로 새 열을 만듭니다.

판다를 여러 열에 '적용' 기능을 사용하려면 어떻게 해야 합니까?

Pandas 데이터 프레임의 두 열에 함수를 적용하는 방법

먼저 Pandas와 NumPy 어레이의 성능은 수치 1어레이의 고성능 벡터화 계산에서 도출됩니다.벡터화된 계산의 전체 포인트는 계산을 고도로 최적화된 C 코드로 이동하고 연속 2메모리 블록을 사용하여 파이썬 수준의 루프를 피하는 것입니다.

파이썬 수준 루프

이제 우리는 몇 가지 타이밍을 볼 수 있습니다.다음은 다음 중 하나를 생성하는 모든 파이썬 레벨 루프입니다.pd.Series,np.ndarray또는list동일한 값을 포함하는 개체입니다.데이터 프레임 내의 영상 시리즈에 할당하기 위해 결과는 비교 가능합니다.

# Python 3.6.5, NumPy 1.14.3, Pandas 0.23.0

np.random.seed(0)
N = 10**5

%timeit list(map(divide, df['A'], df['B']))                                   # 43.9 ms
%timeit np.vectorize(divide)(df['A'], df['B'])                                # 48.1 ms
%timeit [divide(a, b) for a, b in zip(df['A'], df['B'])]                      # 49.4 ms
%timeit [divide(a, b) for a, b in df[['A', 'B']].itertuples(index=False)]     # 112 ms
%timeit df.apply(lambda row: divide(*row), axis=1, raw=True)                  # 760 ms
%timeit df.apply(lambda row: divide(row['A'], row['B']), axis=1)              # 4.83 s
%timeit [divide(row['A'], row['B']) for _, row in df[['A', 'B']].iterrows()]  # 11.6 s

몇 가지 장점:

  1. tuple기반 방법(처음 4)은 다음보다 효율적인 요소입니다.pd.Series기반 메서드(마지막 3개).
  2. np.vectorize이해력 + 이해력 +zip그리고.map방법, 즉 상위 3개는 모두 거의 동일한 성능을 가지고 있습니다.이 그이사때문다니입기하용을 사용하기 입니다.tuple 그리고 머리 위에 있는 판다들을 우회합니다.pd.DataFrame.itertuples.
  3. 를 사용하면 속도가 크게 향상됩니다.raw=True와 함께pd.DataFrame.apply없는 것과 없는 것.을 사용자 합니다. NumPy 배열은 사용자 정의 함수가 .pd.Series물건들.

pd.DataFrame.apply 또 다른 ㅠㅠㅠㅠㅠ

Pandas가 전달하는 물체를 정확하게 보려면 기능을 조금씩 수정할 수 있습니다.

def foo(row):
    print(type(row))
    assert False  # because you only need to see this once
df.apply(lambda row: foo(row), axis=1)

출력:<class 'pandas.core.series.Series'>Pandas 시리즈 개체를 만들고 전달하고 쿼리하면 NumPy 배열에 비해 상당한 오버헤드가 발생합니다.이것은 놀라운 일이 아닙니다: 판다 시리즈에는 지수, 가치, 속성 등을 담을 수 있는 상당한 양의 발판이 포함되어 있습니다.

해보세요.raw=True그리고 당신은 보게 될 것입니다.<class 'numpy.ndarray'>이 모든 것이 문서에 설명되어 있지만, 보는 것이 더 설득력이 있습니다.

np.vectorize 벡터화

의 문서에는 다음과 같은 참고 사항이 있습니다.

는 벡화된함다평가니다합을음을 평가합니다.pyfuncnumpy의 브로드캐스트 규칙을 사용하는 것을 제외하고 파이썬 맵 함수와 같은 입력 배열의 연속적인 튜플 위에.

입력 배열의 차원이 동일하기 때문에 "방송 규칙"은 여기서 중요하지 않습니다. 것사것과 한 것map교육적입니다, 왜냐하면.map위 버전의 성능은 거의 동일합니다.소스 코드는 다음과 같은 상황을 보여줍니다.np.vectorize를 통해 입력 함수를 범용 함수("ufunc")로 변환합니다.캐싱과 같은 몇 가지 최적화 기능이 있어 성능이 다소 향상될 수 있습니다.

대컨요,np.vectorize파이썬 레벨 루프가 수행해야 하는 작업을 수행하지만,pd.DataFrame.apply오버헤드가 많이 발생합니다.JIT 컴파일은 없습니다(아래 참조).그냥 편의상입니다.

실제 벡터화: 사용해야 할 항목

위의 차이점은 왜 어디에도 언급되어 있지 않습니까?실제로 벡터화된 계산의 성능은 관련이 없기 때문입니다.

%timeit np.where(df['B'] == 0, 0, df['A'] / df['B'])       # 1.17 ms
%timeit (df['A'] / df['B']).replace([np.inf, -np.inf], 0)  # 1.96 ms

예, 이는 위의 루프 솔루션 중 가장 빠른 솔루션보다 최대 40배 더 빠른 속도입니다.다음 중 어느 것이든 허용됩니다.제 생각에, 첫 번째는 간결하고, 읽기 쉽고, 효율적입니다. 다른방예법예)만 보십시오.numba성능이 중요하고 병목 현상의 일부일 경우 아래와 같이 처리할 수 있습니다.

numba.njit 향상

루프가 실행 가능한 것으로 간주되는 경우 일반적으로 다음을 통해 최적화됩니다.numbaC로 최대한 많이 이동할 수 있도록 기본 NumPy 배열을 사용합니다.

실로제로▁indeed실.numba성능을 마이크로초로 향상시킵니다.일부 번거로운 작업 없이는 이보다 훨씬 더 효율적으로 작업하기가 어려울 것입니다.

from numba import njit

@njit
def divide(a, b):
    res = np.empty(a.shape)
    for i in range(len(a)):
        if b[i] != 0:
            res[i] = a[i] / b[i]
        else:
            res[i] = 0
    return res

%timeit divide(df['A'].values, df['B'].values)  # 717 µs

용사를 합니다.@njit(parallel=True)더 큰 어레이에 추가적인 성능 향상을 제공할 수 있습니다.


1 숫자 유형은 다음과 같습니다.int,float,datetime,bool,category제외합니다. objectdtype 및 연속 메모리 블록에 보관할 수 있습니다.

2 Python에 비해 NumPy 작업이 효율적인 이유는 최소 2가지입니다.

  • 파이썬의 모든 것은 객체입니다.여기에는 C와 달리 숫자가 포함됩니다.따라서 파이썬 유형에는 네이티브 C 유형에는 존재하지 않는 오버헤드가 있습니다.
  • NumPy 방법은 일반적으로 C 기반입니다.또한 가능한 경우 최적화된 알고리즘이 사용됩니다.

((감, 소이))가 .numpy자체 내부로 이동할 수 있음), 성능이 크게 다르지 않음을 알게 될 것입니다.예:

name_series = pd.Series(np.random.choice(['adam', 'chang', 'eliza', 'odom'], replace=True, size=100000))

def parse_name(name):
    if name.lower().startswith('a'):
        return 'A'
    elif name.lower().startswith('e'):
        return 'E'
    elif name.lower().startswith('i'):
        return 'I'
    elif name.lower().startswith('o'):
        return 'O'
    elif name.lower().startswith('u'):
        return 'U'
    return name

parse_name_vec = np.vectorize(parse_name)

몇 가지 시간을 지정하는 중:

적용 사용

%timeit name_series.apply(parse_name)

결과:

76.2 ms ± 626 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

용사를 합니다.np.vectorize

%timeit parse_name_vec(name_series)

결과:

77.3 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy로 .ufunc호출할 때 객체np.vectorize어떻게 이런 일을 하는지, 저는 잘 모르겠습니다. 제가 기꺼이 ATM을 이용하는 것보다 더 많은 numpy의 내부를 파헤쳐야 할 것입니다.그렇긴 하지만, 여기서는 문자열 기반의 함수보다 단순한 수치 함수에서 더 잘 하는 것 같습니다.

크기를 최대 1,000,000까지 크랭킹:

name_series = pd.Series(np.random.choice(['adam', 'chang', 'eliza', 'odom'], replace=True, size=1000000))

apply

%timeit name_series.apply(parse_name)

결과:

769 ms ± 5.88 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

np.vectorize

%timeit parse_name_vec(name_series)

결과:

794 ms ± 4.85 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

보다 나은(벡터화된) 방법np.select:

cases = [
    name_series.str.lower().str.startswith('a'), name_series.str.lower().str.startswith('e'),
    name_series.str.lower().str.startswith('i'), name_series.str.lower().str.startswith('o'),
    name_series.str.lower().str.startswith('u')
]
replacements = 'A E I O U'.split()

시간:

%timeit np.select(cases, replacements, default=name_series)

결과:

67.2 ms ± 683 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

언급URL : https://stackoverflow.com/questions/52673285/performance-of-pandas-apply-vs-np-vectorize-to-create-new-column-from-existing-c

반응형