반응형

Python으로 데이터를 다루는 업무에서 Pandas는 거의 필수적이다. Pandas는 쉽고, 빠르고(Python에 비해), 유용하고, 간편하기 때문이다.  또한, 테이블 형태의 구조를 가지고 있는 pandas DataFrame은 매우 친숙하고, 안정적인 느낌을 준다. 평소에는 Pandas의 사용하는 기능만 사용하고, 필요한 기능이 있으면 그때그때 알아보면서 사용하고 있는데, 이번 기회에 Pandas의 함수를 정리해보고자 한다. 

(사실 pandas의 전체 함수를 정리하는 것은 거의 불가능하다. 내 기준으로 많이 사용하고 유용할 것 같은 함수를 정리했다. 필요한 함수가 있다면 https://pandas.pydata.org/docs/reference/index.html 를 직접 참조하는 것이 좋을 것이다.)


Pandas의 데이터 구조

  • Series : Pandas의 series는 1D array와 유사한 데이터구조로, index와 value로 구성되어 있다. Series가 1D의 형태를 가지고 있다 보니, axis가 1개 존재하는데, 이 axis를 index라고 한다. value로는 ndarray나, dictionary, 스칼라 값들이 들어갈 수 있다.
  • DataFrame : DatFrame은 2D의 데이터 구조로, SQL 테이블과 비슷하게, column과 row로 구성되어있다. DataFrame의 각혈은 Series로, DataFrame은 칼럼명이 key인 Series들의 dictionary로 생각할 수 있다. DataFrame은 Series나 2D ndarray, 다른 DataFrame, 1D 데이터(ndarray, list, Series 등등..)들로 구성된 Dictionary를 통해 만들 수 있다.
  • 기본적으로 DataFrame은 Numpy를 기반으로 만들어졌다. 따라서, Series는 Numpy 1D 배열(ndarray)과 동일하다고 생각하면 된다. 때문에, numpy에 있는 거의 모든 함수들이 Pandas 데이터 구조에 호환된다.  

 

Pandas의 함수

[Indexing/Selection]

  • Indexing/Selection은 Python의 리스트처럼 매우 직관적이여서, 공식 사이트의 부분으로 설명을 대체한다.

  • query : 위에서 나온 DataFrame 기본 indexing 말고도, SQL 형식으로 DataFrame에서 조건에 맞는 row를 가지고 올 수 있다. 
import seaborn as sns

if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")
    print("[query 출력값]")
    print(titanic_data.query('age > 30'))

 

  • between : 특정 숫자형 열의 범위 내의 데이터를 filtering하기 위해 사용할 수 있는 함수이다. 
import seaborn as sns

if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")
    print("[between 출력값]")
    print(titanic_data[titanic_data['age'].between(30,40)])

 

[데이터 연산]

  • 사실 Series와 DataFrame이 많이 사용되는 이유이기도 한데, Series나 DataFrame 끼리 연산이 된다.
  • 단순 연산이 간단할 뿐 아니라, 간단한 연산 위주로 수행한다면, 속도가 매우 빠르다. (Pandas는 numpy로 구성되고, numpy는 C로 변환하여 연산을 하기 때문에)
import pandas as pd
import numpy as np

if __name__ == '__main__':

    df1 = pd.DataFrame(np.arange(10))
    df2 = pd.DataFrame(np.arange(10)*2)

    print(df1+df2)
    print(df1-df2)
    print(df1*df2)
    print(df1/df2)

 

[데이터 읽기&쓰기]

  • read_csv, read_excel : 각각 csv 형태, excel 형태를 쉽게 읽어서, DataFrame 형태로 반환한다.
  • to_csv, to_execel : DataFrame을 각각 csv, excel 형태로 export 한다.

 

[데이터 요약&추출]

  • head : DataFrame의 처음 N개(인자값) 행을 출력한다. (default:5) 
  • tail :  DataFrame의 마지막 N개(인자값) 행을 출력한다. (default:5) 
  • sample : DataFrame에서 무작위 행 N개를(인자값) 추출한다. (default:1), DataFrame의 행을 무작위로 섞고 싶다면, frac 인자(추출 비율)를 1로 지정하면, 전체 DataFrame이 무작위로 섞여서 나온다. 

  • describe : DataFrame의 칼럼 중, 숫자 칼럼에 대한 통계값(평균, 표준편차, 최솟값, 사분위수, 백분위수, 최댓값)을 표현해 준다.

  • value_counts : Series 객체 내의 unique value의 빈도를 반환한다. Categorical 데이터 개수 파악에 주로 사용한다.

  • dtypes : dtypes는 함수가 아닌 속성이다. 각 열의 데이터 유형을 출력한다. pandas의 DataFrame은 기본적으로 열의 데이터 유형을 추정하는 방식을 사용하기 때문에, 추정한 유형을 알기 위해 출력이 필요하다.
  • info : DataFrame의 각 칼럼 값과, Index 범위, not null 인자 개수, 데이터 타입, 메모리 정보를 출력해 준다. 

 

import seaborn as sns

if __name__ == '__name__':
    titanic_data = sns.load_dataset("titanic")

    print("[head 출력값]")
    print(titanic_data.head(5))
    print("[tail 출력값]")
    print(titanic_data.tail(5))
    print("[sample 출력값]")
    print(titanic_data.sample(5))
    print("[describe 출력값]")
    print(titanic_data.describe())
    print("[value_counts 출력값]")
    print(titanic_data.value_counts())
    print("[dtypes 출력값]")
    print(titanic_data.dtypes)
    print("[info 출력값]")
    print(titanic_data.info())

 

[데이터 결합]

  • combine_first : 두 DataFrame을 결합할 때, 첫 번째 DataFrame이 누락된 경우, 두 번째 DataFrame의 값으로 대체하는 값이다. DataFrame을 이용한 coalesce or nvl 함수라고 보면 된다. 어떠한 DataFrame에 비어있는 값을 다른 DataFrame의 값으로 대체하기 위한 함수이다. 
  • combine : 두 DataFrame을 결합할 때, 개발자가 정의한 함수에 따라서 결합하기 위한 함수이다. combine_first가 단순히 하나의 DataFrame에 없는 값을 대체해 주는 것에 비해, combine은 결합 조건 및 결합 방식을 직접 정의 가능하다.
  • concat : DataFrame을 단순히 쌓는다. axis가 0이면, row에 append 하는 구조로 DataFrame을 결합하고, axis가 1이면 칼럼이 늘어나는 방식이다. 
  • join : SQL에서의 join처럼 두 DataFrame 간의 join을 위해 사용되는 함수이다. 다만, 인덱스 기준으로 DataFrame을 결합하기 때문에, index를 가공하지 않으면 사용이 제한적이다. join 방법을 지정하는 how 인자에 left, right, inner, outer를 지정할 수 있다.(default left)
  • merge : 조금 더, 유연한 방식의 join 방법이다. join은 DataFrame 함수이고, merge는 pandas 함수이기 때문에, join은 인덱스 기준으로만 join이 가능한 반면, merge는 join key를 지정해 줄 수 있다. 인자는 다양하지만, join key를 지정하는 "on", join 방식을 결정하는 "how" 정도만 사용하면 된다. "how" 인자로는 join과 마찬가지로  left, right, inner, outer를 지정할 수 있지만, default 값이 inner join이다. 
import pandas as pd
import numpy as np

if __name__ == '__main__':
    df1 = pd.DataFrame({'data': [1, 2, np.nan, 4, 5, 6]}, index=[np.arange(6)])
    df2 = pd.DataFrame({'data': [0, 0, 5, 0, 0, 0]}, index=[np.arange(6)])

    df1.columns = ['key']
    df2.columns = ['key']

    combine_df1 = df1.combine_first(df2)
    combine_df2 = df1.combine(df2, lambda x, y: x ** 2 + y ** 2)
    concat_df1 = pd.concat([df1, df2], axis=0)
    concat_df2 = pd.concat([df1, df2], axis=1)
    join_df = df1.join(df2, how='inner',lsuffix='left', rsuffix='right')
    merge_df = pd.merge(df1, df2, on='index', how='inner')

    print("[combine_first 출력값]")
    print(combine_df1)
    print("[combine 출력값]")
    print(combine_df2)
    print("[concat 행 방향 출력값]")
    print(concat_df1)
    print("[concat 열 방향 출력값]")
    print(concat_df2)
    print("[join 출력값]")
    print(join_df)
    print("[merge 출력값]")
    print(merge_df)

 

 

[데이터 변환]

  • apply : pandas에서 가장 유용한 함수라고 생각한다. 인자로 함수를 넣어주면, DataFrame의 각 행에 함수를 적용할 수 있다. 일반적으로 간단한 연산들은 단순 반복문보다 apply로 처리하는 게 속도적으로 유리하다.
  • fillna : DataFrame이나 Series의 누락된 값을 다른 값으로 채울 수 있다. 보통 결측치를 보정하는 데 사용한다.
  • replace : DataFrame의 특정 값을 다른 값으로 변환해 준다.
  • map : dictionary 형태의 mapping을 통해, DataFrame 내의 값을 다른 값으로 mapping 해줄 수 있다. 
import seaborn as sns

if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")

    print("[apply 출력값]")
    print(titanic_data['fare'].apply(lambda x: (x+10)*2))
    print("[fillna 출력값]")
    print(titanic_data['age'].fillna(titanic_data['age'].mean()))
    print("[replace 출력값]")
    print(titanic_data['sex'].replace('male', 1))
    print("[map 출력값]")
    print(titanic_data['sex'].map({'male':1, 'female':2}))

 

[집계 함수]

  • mean, max, min, sum, median, std, count : pandas의 DataFrame은 Table 형태의 구조를 띄고 있기 때문에, 집계가 매우 용이하다. DataFrame의 특정 칼럼을 지정하고, 해당 함수를 사용하면, 칼럼의 통계값을 쉽게 얻을 수 있다. 
  • agg : 집계를 한 번에 적용할 때, 쉽게 사용할 수 있다. 보통 groupby와 함께 자주 사용된다. axis가 0이면 칼럼별로, 1이면 행별로 함수를 적용한다.
  • cut : 연속형 데이터를 구간으로 나누는 데 사용된다. 
if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")

    print("[max 출력값]")
    print(titanic_data['fare'].max())
    print("[agg 출력값]")
    print(titanic_data['fare'].agg(['max', 'min', 'mean'], axis=0))
    print("[cut 출력값]")
    bins = [0, 18, 35, 50, 100]
    labels = ['미성년자', '청년', '중년', '노년']
    print(pd.cut(titanic_data['age'], bins=bins, labels=labels))

 

 

[정렬 함수]

  • sort_values : SQL의 orderby 함수처럼 특정 칼럼의 값에 따라 정렬할 수 있다. 기본적으로 오름차순으로 정렬한다."by" 인자에 열을 명시해줘야 한다.
  • sort_index : Index 기반으로 DataFrame을 정렬한다. 마찬가지로 기본적으로 오름차순으로 정렬한다.
  • nsamllest, nlargest : 각각 DataFrame 내에서 가장 큰 값, 가장 작은 값 N개를 가진 행을 찾는 데 사용된다. 
if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")

    print("[sort_values 출력값]")
    print(titanic_data.sort_values(by='age'))
    print("[sort_values 출력값]")
    print(titanic_data.sort_values(by='age'))
    print("[sort_index 출력값]")
    titanic_data = titanic_data.sample(frac=1)
    print(titanic_data.sort_index())
    print("[nlargest 출력값]")
    print(titanic_data["age"].nlargest(5))
    print("[nsamllest 출력값]")
    print(titanic_data["age"].nsmallest(5))

 

 

[그 밖 유용한 함수들]

  • groupby : SQL의 groupby처럼 DataFrame을 grouping 하여, group 별 집계, 연산에 사용하는 함수이다. groupby key를 지정해 주면, 해당 기준으로 grouping 된다. 
if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")

    group_titanic_data = titanic_data.groupby('sex')
    print(group_titanic_data)
    print(group_titanic_data["fare"].max())
    print(group_titanic_data["fare"].mean())
    print(group_titanic_data["fare"].min())
    print(group_titanic_data["fare"].agg(['max', 'mean', 'count']))

  • explode : SQL의 Unnest와 같다. DataFrame 내에 List 또는 Series로 저장된 요소들을 분해하여 풀어내는 데 사용된다. 길이가 같은 여러 칼럼의 list를 한 번에 풀 수 있는 게 매우 유용하다. (spark에선 이 기능이 직관적이지 않다.)
if __name__ == '__main__':
    titanic_data = sns.load_dataset("titanic")

    group_titanic_data = titanic_data.groupby('sex')
    # group by를 통해 각 age 요소를 list 형태로 만듬
    group_data = group_titanic_data['age'].agg(list).reset_index()
    print(group_data)
    print(group_data.explode('age'))

 

 

반응형

Pandas 란?

  • Pandas는 파이썬에서 데이터 처리와 분석을 위한 라이브러리로, numpy를 기반으로 개발되었다.
  • Pandas는 DataFrame과 Series라는 데이터 구조를 사용하여, 데이터를 쉽게 처리할 수 있도록 한다.
  • Pandas는 C 언어 등, low level의 언어로 개발된 numpy를 기반으로 하고, 수년간의 버전 업그레이드로 그 자체에 최적화를 해놓았기 때문에, 일반적으로 Pandas에서 제공하는 함수들을 사용하는 것이 성능 면에서 가장 좋다.
  • 하지만, 데이터 크기와 연산의 복잡성에 따라, 특정한 상황에서는 Pandas의 성능을 최적화하기 위한 방법이 필요하다.

 

Pandas의 데이터 처리 최적화 원리

  • Pandas는 기본적으로 low level 언어로 최적화가 많이 되었기 때문에, Pandas 데이터 처리를 위한 연산 과정에 Python 언어로 처리하는 과정이 생략되는 것이 좋다. 
  • Pandas는 메모리 위에서 동작한다. 이에 따라, 메모리의 가용 용량을 벗어나는 데이터 처리 및 연산은 한 번에 처리할 수 없다. 따라서, 메모리를 효율적으로 사용할 수 있도록 변경하는 것이 좋다.

 

Pandas 데이터 로드

  • 사실, Pandas를 많이 활용하는 이유 중 하나는 Pandas의 Dataframe이  SQL 테이블 형태와 거의 유사하기 때문이다.
  • Pandas에 들어갈 데이터를 Code 내부에서 주입하는 경우도 있지만, 대부분의 경우, Database나, CSV File 등에서 Import 해오는 경우가 많다.
  •  Pandas는 앞서 말한대로, 메모리에 DataFrame을 올려놓고, 연산하는 형태이기 때문에, 메모리가 연산을 효율적으로 처리할 수 있도록, 작은 단위의 필요한 데이터만 사용하는 것이 연산 측면에서 유리하다.

 

1. Query 및 파일 최적화

  • Pandas에서 SQL이나 CSV 등의 Raw 형태의 데이터를 읽고, 이를 Filtering하여Filtering 하여 사용하는 경우가 많은데, 이는 Raw 데이터 전체를 메모리 올려, 메모리 & I/O 부담을 증가시킨다. 따라서, 필요한 데이터만 미리 Filtering 하여 가져오는 것이 좋다.
  • 이렇게 Pandas에서 필요한 데이터만 가져오면, 전체 Series의 갯수(Row 수)가 줄기 때문에, Index 활용 측면에서도 유리하다.
  • 예시로, 한국어 형태소 분석을 위해 SQL 테이블에서 1주일치 데이터를 읽어서, 처리하였는데, 하루씩 읽어서 7번 처리하는 게 속도 면에서 더 효율적이었다.  
  • 마찬가지로, 필요한 칼럼만 가져오는 것이 성능 면에서 유리하다.
df = pd.read_csv('raw_data.csv', usecols=['col1', 'col2', ...])

 

2. Data Type 미리 지정

  • Pandas는 자료 구조형 선언의 제약을 받지 않는 파이썬 위에서 돌아가기 때문에, 읽어온 데이터를 통해 Data Type을 추론하는 과정이 포함된다. 
  • Data Type을 사용자가 미리 지정하면, 1) Data Type 추론 과정을 줄일 수 있고, 2) 실제 필요한 데이터 크기에 맞는 정도만 메모리를 할당하기 때문에, 성능 면에서 유리하다.
  • 다만, Data Type을 미리 지정하는 것은, 추후 연산 시에 메모리를 효율적으로 사용할 수 있다는 장점이 있지만, 읽는 속도 자체에는 큰 영향을 미치지 않는다.
dtypes = {'col1': 'int', 'col2': 'float', ...}
df = pd.read_csv('raw_data.csv', dtype=dtypes)

 

3. chunk 옵션 사용

  • 위의 방법을 사용해도, 어쩔수 없이 대용량 데이터를 모두 사용해야 하는 경우가 많다.
  • 이와 같은 경우에는 DataFrame을 chunk 단위로 처리하는 것이 효율적이다.
  • chuncksize를 지정해줘서, 한 번에 읽을(메모리에 올릴) Series(row) 수를 지정할 수 있다.
  • 하지만, 전체 데이터가 같이 필요한 것들(특정 칼럼 sort 등)은 처리가 까다롭기 때문에, 다른 행들 간의 연산이 비교적 적은 경우에 활용하는 것이 좋다.
for chunk in pd.read_csv('raw_data.csv', chunksize=10000):
    processing(chunk)

 

4. Dask 사용

  • 만약, 메모리가 감당하기 어려운 정도의 어려운 정도의 데이터 양과 연산이 포함된다면, 대용량 데이터를 분산 처리하기 위해 개발된, Dask를 사용할 수 있다.
  • Dask는 Pandas와 달리, Disk에 저장된 데이터를 처리하기 때문에, 여러 머신에서 분산처리가 가능하고, 지연 연산을 사용하기 때문에, 실제 연산을 최적화하는 과정이 포함된다.(SQL의 실행 Plan을 생각하면 된다.) 따라서, 초 대용량 데이터 처리에는 Dask의 강점이 있다.
  • 하지만, 메모리가 감당 가능한 수준의 연산에서는 메모리와 디스크의 속도 차이 등 때문에, Pandas가 유리하다.
import dask.dataframe as dd
if __name__ == "__main__":
	df = dd.read_csv('raw_data.csv')

 

 

Pandas 연산 & 조회

[실험 데이터셋]

  • Pandas 연산 테스트를 위해, 예시 데이터로 Kaggle 데이터셋을 사용했다. (https://www.kaggle.com/datasets/jordankrishnayah/45m-headlines-from-2007-2022-10-largest-sites?resource=download)
  • 사용할 데이터는, 4.5M 분량의 2007년부터 2022년 주요 언론사의 기사 제목 headline 데이터이다. 데이터는 총, 4405392개 row로 구성되어 있고, [Date, Publication, Headline, URL]의 4개의 칼럼으로 구성되어 있다.

 

1. 반복문 최적화

  • Pandas 연산에서 가장 큰 성능 개선 포인트는 반복문 연산이다.
  • Pandas가 Python 언어로 동작하기 때문에, Python의 list의 개념으로 Dataframe을 다루기 때문에, 이런 문제가 많이 발생한다.
  • Pandas를 For문을 통해, 각 row에 접근하는 경우에는, 각 row마다 연산을 각각 실행한다. 이에 따라, 데이터 크기가 커질수록 연산의 Overhead는 가중화된다.
  • 가장 쉬운 방법은 Pandas의 apply를 사용하는 것이다. 
  • 또한, Numpy Vectorize를 사용하는 방법, iterrows, itertuples를 사용하는 방법들이 있는데, 일반적으로 itertuples와 numpy vectorize는 Pandas Apply보다 좋은 성능을 보인다고 알려져 있다.
  • 추가적으로 멀티스레드를 이용하여, Pandas 연산을 병렬화 하고, 효율적으로 처리하는 swifter가 있다.

 

(테스트 상황)

  • headline 데이터셋에서 URL의 "http://" or "https://" 부분을 제거하는 작업을 테스트
  • headline 데이터셋에서 Date의 연도를 제거하는 작업을 테스트

 

(1) 단순 For문

for i in range(data.shape[0]):
    data["URL"][i] = data["URL"][i].replace('http://', '').replace('https://', '')

→ 실행 시간 : 94848.09 s(1000 row 실행시간: 21.53으로 추정)

 

for i in range(data.shape[0]):
    data["Date"][i] = data["Date"][i]%10000

→ 실행 시간 : 572.70s(1000 row 실행시간: 0.13으로 추정)

 

 

(2) Pandas Apply

data["URL"] = data["URL"].apply(lambda x: x.replace('http://', '').replace('https://', ''))

→ 실행 시간 : 1.47s

data["Date"] = data["Date"].apply(lambda x: x % 10000)

→ 실행 시간 : 0.88s

 

 

(3) Numpy Vectorize

  • numpy에서 제공하는 vectorize를 통해, 연산하고자 하는 함수를 vectorize화 할 수 있다.
  • numpy를 import 하여 쉽게 사용할 수 있다.
import numpy as np
vectorize_function = np.vectorize(lambda x: x.replace('http://', '').replace('https://', ''))
data["URL"] = vectorize_function(data["URL"])

→ 실행 시간 : 59.09s

import numpy as np
vectorize_function = np.vectorize(lambda x: x % 10000)
data["Date"] = vectorize_function(data["Date"])

→ 실행 시간 : 0.46s

 

(4) Vector화 된, For문 사용

  • Pandas는 numpy로 만들어졌기 때문에, numpy의 데이터를 조회하기 위한 iterator의 순회문을 사용하면, 빠르게 데이터를 순회할 수 있다.

[iterrows]

temp_date = []
for i, row in data.iterrows():
	temp_url.append(row["URL"].replace('http://', '').replace('https://', ''))
data["URL"] = temp_url

→ 실행 시간 : 88.10s

temp_date = []
for i, row in data.iterrows():
	temp_date.append(row["Date"]%10000)
data["Date"] = temp_date

→ 실행 시간 : 86.14s

 

 

[itertuples]

temp_url = []
for row in data.itertuples(index=False):
    temp_url.append(row.URL.replace('http://', '').replace('https://', ''))
data["URL"] = temp_url

→ 실행 시간 : 3.37s

temp_date = []
for row in data.itertuples(index=False):
	temp_date.append(row.Date%10000)
data["Date"] = temp_date

→ 실행 시간 : 2.69s

 

 

(5) Swifter

  • Swifter는 pandas의 apply를 멀티스레드를 통해, 병렬화하여 빠르게 처리하도록 하는 파이썬 패키지이다.
  • pip install swifter를 통해, 설치 가능하다.
data["URL"] = data["URL"].swifter.apply(lambda x: x.replace('http://', '').replace('https://', ''))

→ 실행 시간 : 3.98s

data["Date"] = data["Date"].swifter.apply(lambda x: x % 10000)

→ 실행 시간 : 0.37s

 

[실험 결과] 

  • Python 영역의 연산을 활용하는 경우(Python 라이브러리 or String 사용 등)에는 apply나 itertuples를 사용한 순회가 가장 좋은 성능을 보인다. → 첫 번째 실험
  • Python 영역의 연산을 활용하지 않는 경우, 즉, Cython으로 변환이 가능한 연산등은 np.vectorize가 가장 좋은 성능을 보인다.
  • 데이터의 타입, 크기, 연산에 따라, 가장 적합한 연산은 다르겠지만,
    • 일반적으로 apply나 itertuples를 사용한 순회가 가장 좋다.(일반적으로 대용량에선 itertuples가 apply보다 낫다고 함.)
    • 연산이 간단한 경우(Cython으로 변환이 될만한 간단한 연산)에는 np.vectorize를 통해 최적화가 가능하다.
    • 단순 for문은 사용하지 않는 것이 좋다. 
    • Swifter는 데이터의 크기가 매우 크고, 연산이 복잡하지 않은 연산에서 효과적이다.

 

2. 특정 조건 데이터 조회

  • 특정 조건 데이터 조회는 Pandas에서 자주 사용된다. 
  • 연산에 비해, 긴 시간이 걸리지는 않지만, 데이터가 많고, 연산이 복잡해질수록 조건에 맞는 데이터를 찾는 시간이 오래 걸린다.

(테스트 상황)

  • headline 데이터셋에서 2022년부터 데이터 중, New York Times의 데이터를 찾으려고 한다.

 

(1) Boolean Type으로 indexing

  • 가장 일반적인 방법이다. 여러 개의 칼럼들의 조건의 boolean 형태로 각각 연산하여 구할 True인 값만 가져올 수 있다.
filtered_data = data[(data["Date"]>20220000) & (data["Publication"]=='New York Times')]

→ 실행 시간 :0.14s

 

 

(2) loc를 이용한 indexing

  • Boolean Type으로 indexing과 동일하다.
filtered_data = data.loc[(data["Date"]>20220000) & (data["Publication"]=='New York Times')]

→ 실행 시간 :0.14s

 

 

 

(3) query를 사용한 조회

  • Dataframe은 query를 지원한다. (하지만, like 등의 조건은 지원하지 않는다.)
  • 참조하는 칼럼이 많고, 데이터가 클수록, query를 내부적으로 최적화하는 단계가 있어 더 좋은 성능을 보인다.
filtered_data = data.query("Date >20220000 and Publication == 'New York Times'")

→ 실행 시간 :0.07s

 

 

 

(4) isin을 사용한 indexing

  • 큰 범위애서 보면, Boolean Type으로 indexing에 속하는데, 특정 문자열과 일치하는 조건을 찾을 때는, boolean type에 isin을 넣어주면 더 빨리 찾을 수 있다.
filtered_data = data[(data["Publication"].isin(['New York Times'])) & (data["Date"] > 20220000)]

→ 실행 시간 :0.05s

 

 

(5) itertuples를 사용한 순회

  • 순회를 이용한 데이터 indexing은 별로 좋은 방법은 아니다.
  • 하지만, 연산과 조회를 같이하는 경우에는 한 번의 순회에 조회 조건을 넣어, 데이터를 찾는 것도 고려해 볼 수 있다.
find_index = []
for i, row in enumerate(data.itertuples(index=False), start=0):
    if row.Date > 20220000 and row.Publication == 'New York Times':
        find_index.append(i)

filtered_data = data.iloc[find_index]

→ 실행 시간 :2.01s

 

[실험 결과] 

  • 일반적으로 사용되는 boolean을 사용하는 것이 좋다고 알려져 있지만, 참조하는 칼럼이 많고, 데이터가 많을 경우에는 query를 사용하는 것이 효과적일 수 있다.
  • Boolean Type도 단순히 조건을 넣어서 indexing 하는 것보다, isin  같은 pandas 연산자를 함께 사용해서 데이터를 찾는 것이 효율적이다.

 

3. 문자열 포함 검색

  • SQL에서는 LIKE라는 특정 문자열을 포함했는지 여부를 찾는 방법이 있지만, Pandas에서는 LIKE를 지원하지 않는다. 
  • 생각보다, 특정 문자를 포함하는지 여부를 점검하는 경우가 많은데, 이런 경우 어느 방법이 효과적일까?

 

(테스트 상황)

  • headline 데이터셋에서 URL이 https 형식을 사용하는 row만 추출하려 한다.

(1) Pandas str contains를 사용한 검색

  • Pandas에서는 문자열 존재 여부를 체크해 주는 str.contains가 존재한다. 
filtered_data = data[data["URL"].str.contains("https://")]

→ 실행 시간 :1.18s

 

 

(2) apply를 통한, indexing (in)

  • Python에서 특정 문자가 포함되었는지 여부는 in을 통해 쉽게 파악할 수 있다.
  • pandas의 apply를 통한 indexing을 이용해 쉽게 문자열을 검색할 수 있다.
filtered_data = data[data["URL"].apply(lambda x: "https://" in x)]

→ 실행 시간 : 0.62s

 

 

(3) apply를 통한, indexing (startswith)

  • in과 마찬가지지만, in은 위치를 특정해주지는 못한다. 이럴 때는 startswith나 endwith 등을 사용하여 indexing 할 수 있다.
filtered_data = data[data["URL"].apply(lambda x: x.startswith("https://"))]

→ 실행 시간 : 0.69s

 

 

(4) apply를 통한, indexing (re)

  • in과 statrswith 등으로 문자열 검색이 가능하지만, 실제 검색하고자 하는 데이터들은 특정 형태를 가지고 있는 경우가 많다. 
  • 이런 경우, 보통 for문을 통한 순회를 가장 먼저 생각하는데, 위에서 보인대로, 단순 for문은 너무 많은 시간이 걸린다.
  • 이런 경우, apply에 re를 사용하여, 정규 표현식 형태로 문자를 검색할 수 있다.
import re
filtered_data = data[data["URL"].apply(lambda x: True if re.search("https://", x) else False)]

→ 실행 시간 : 2.38s

 

 

[실험 결과] 

  • str contains보다, pandas apply를 통해, boolean 형태의 output을 내는 함수를 정의하고, 이것을 indexing 하는 것이 더 빠르다.
  • 가장 중요한 것은 단순 반복문 형태의 순회는 최대한 지양하는 것이 성능 측면에서 도움이 된다.

 

 

+ Recent posts