본문 바로가기
프로그래밍 언어(Programming Languages)/파이썬(Python)

[Python] 빅데이터? 문제 없다! 데이터 처리 속도를 10배 높이는 파이썬 벡터화 활용법

by 데이터 벌집 2023. 10. 8.

Loop vs. Vectorization

프로그래밍을 배우면서, 대부분의 사람들이 가장 먼저 마주치는 것은 '반복문(Loop)'입니다. 이는 프로그래밍의 기본이자, 데이터의 순회와 조작을 위해 널리 사용되는 기능입니다. 특히 파이썬에서 for나 while 같은 루프를 활용해 리스트나 배열, 그리고 다양한 데이터 구조를 쉽게 처리할 수 있습니다.

 

그러나 대규모 빅데이터, 특히 수백만, 수십억 행의 데이터를 처리해야 하는 경우, 일반적 반복문을 사용하는 것은 굉장히 비효율적입니다. 루프를 돌면서 각 행을 개별적으로 처리하는 과정은 상당한 시간이 소요되며, 이로 인해 프로그램의 성능을 저하될 수 있습니다.

 

이러한 문제를 해결하기 위해 데이터 과학자들과 프로그래머들은 '벡터화'라는 기술에 주목하고 있습니다. 벡터화는 NumPy와 같은 라이브러리를 활용해 전체 데이터셋에 대한 연산을 한 번에 처리하는 방식을 말합니다. 이를 통해 개별 행에 대해 연산을 수행하는 대신, 전체 배열이나 시리즈에 대해 동시에 연산이 가능해지며, 이로 인해 연산 속도가 획기적으로 개선됩니다.

 

본 글에서는 왜 벡터화가 중요한지, 그리고 벡터화를 통해 파이썬 코드를 어떻게 최적화할 수 있는지를 소개하고자 합니다. 루프에 의존한 코딩에서 벗어나, 더 빠르고 효율적인 코드 작성의 비밀에 대해 함께 알아보겠습니다.

 

 

Loops vs. Vectorization 비교


백터화는 무엇인가?

벡터화는 배열이나 시리즈의 모든 요소에 연산을 한 번에 적용하는 테크닉입니다. 이 방식은 특히 NumPy 라이브러리에서 효과적으로 작동합니다. 일반적인 루프에 비해 계산이 빠르며 코드도 간결해집니다. 루프를 사용하면 각 요소를 개별적으로 처리해야 하지만 벡터화를 통해 이러한 과정 없이도 여러 요소를 한 번에 처리할 수 있습니다.


실전 벡터화 예제

예제 1: 행렬 곱셈

 

루프 사용

 

루프를 사용해 행렬 곱셈을 수행할 경우, 각 행렬의 원소를 하나씩 확인하고 곱하고 연산해야 합니다. 이는 코드가 길어지고 실행속도가 느려질 수 있습니다.

start = time.time()

A = np.random.rand(100, 100)
B = np.random.rand(100, 100)
C = np.zeros((100, 100))

start_time = time.time()

for i in range(100):
    for j in range(100):
        for k in range(100):
            C[i][j] += A[i][k] * B[k][j]

end_time = time.time()
print(f"Loop-based matrix multiplication time: {end_time - start_time:.6f} seconds")
# Loop-based matrix multiplication time: 0.675444 seconds

 

백터화 사용

 

반면, 백터화를 사용하면 행렬곱셈을 더 간결하고 빠르게 수행할 수 있습니다. numpy의 'dot' 함수를 사용하면 한 줄의 코드로 행렬곱셈을 더 빠르게 수행할 수 있습니다.

start_time = time.time()

C = np.dot(A, B)

end_time = time.time()
print(f"Vectorized matrix multiplication time: {end_time - start_time:.6f} seconds")
# Vectorized matrix multiplication time: 0.013633 seconds

 

예제 2: 데이터프레임 

 

데이터프레임은 행과 열로 구성된 테이블 형식의 데이터 구조입니다. 데이터프레임을 처리할 때는 루프를 사용해 각 행이나 열을 개별적으로 처리하는 경우가 많습니다.

루프 사용

df = pd.DataFrame({'a': range(1, 1000001)})
start_time = time.time()

for idx, row in df.iterrows():
    df.at[idx, 'a_squared'] = row['a'] ** 2

end_time = time.time()
print(f"Loop-based dataframe time: {end_time - start_time:.6f} seconds")

# Loop-based dataframe time: 22.561701 seconds

 

백터화 사용

start_time = time.time()
df['a_squared'] = df['a'] ** 2

end_time = time.time()
print(f"Vectorized dataframe time: {end_time - start_time:.6f} seconds")

# Vectorized dataframe time: 0.005331 seconds

예제 3: 조건부 로직

루프 사용

some_array = np.random.randint(0, 10000000, size=10000000)

start_time = time.time()

result = []
for val in some_array:
    if val > 10:
        result.append(True)
    else:
        result.append(False)

end_time = time.time()
print(f"Loop-based condition logic time: {end_time - start_time:.6f} seconds")

# Loop-based condition logic time: 1.399790 seconds

백터화 사용

start_time = time.time()

result = some_array > 10

end_time = time.time()
print(f"Vectorized condition logic time: {end_time - start_time:.6f} seconds")

# Vectorized condition logic time: 0.053172 seconds

10년 전 코딩을 시작할 때, 저는 자주 for 루프를 사용했습니다. 그러나 시간이 흐르면서 대량의 데이터를 빠르게 처리해야 하는 필요성을 느끼게 되었고, 이에 '벡터화' 기술을 발견했습니다. 벡터화를 통해 코드가 간결해지는 동시에, 실행 속도가 훨씬 빨라져 고객의 만족도를 높일 수 있었습니다. 데이터가 계속 커지는 추세에서 벡터화는 더욱 중요한 기술로 자리 잡을 것입니다. 이 기술을 활용하여 여러분도 효율적이고 빠른 프로그래밍을 경험하시길 바랍니다.