#Relational_API #DuckDBPyRelation #Lazy
DuckDB 를 관계형 API 로 다루는 방법.
** 참고
https://duckdb.org/docs/api/python/relational_api
이전 포스팅에 이어서 nyc 데이터를 활용해보면
import duckdb
# 로컬 DB 를 활용하면서
con = duckdb.connect('./my-db.duckdb')
# 로컬 환경에 맞는 데이터 PATH 를 입력하고,
file_path = './nyc_data/*.parquet'
con.execute(f"""
CREATE TABLE nyc_tbl AS SELECT * FROM read_parquet('{file_path}')
""")
이러면 로컬 DB 파일에 parquet 파일 내용이 저장 된다.
이러고 다른 세션(쥬피터 or 터미널) 에서 해당 .duckdb 파일로 연결하면 nyc_tbl 테이블을 읽고 사용할 수 있다.
간단하게 데이터를 확인하면
con.sql("""
select * from nyc_tbl limit 5
""").show()
이제 이걸 관계형 API 로 사용하기 위해서 변수에 테이블을 담아서 duckdb.duckdb.DuckDBPyRelation 라는 타입 결과로 만들어 준다.
nyc_table = con.table('nyc_tbl')
print(type(nyc_table))
>> duckdb.duckdb.DuckDBPyRelation
바로 관계형 API 가 무엇인지 대략 감이 오는 심플한 예시를 보자면
nyc_table.pl()
pl() 이 바로 polars DataFrame 으로 결과를 반환하는 API 다.
이제 시나리오를 통해서 몇가지 더 확인해보면
우선
1. filter() 를 통해서 fare_amount 와 trip_distance 가 0보다 큰 값만 추려내고,
2. project() 로 VendorID, tpep_pickup_datetime, tpep_dropoff_datetime, trip_distance, fare_amount, total_amount 해당 컬럼만 추출하고,
3. aggregate() 를 이용해서 timestamp 를 1시간 간격으로 roll over 하고 각 숫자 데이터의 값을 AVG 로 표현한다.
그냥 딱 코드보면 SQL 이 생각이 날 법 한데
nyc_table.filter("""
fare_amount > 0 and trip_distance > 0
"""
).project("""
VendorID, tpep_pickup_datetime, tpep_dropoff_datetime, trip_distance, fare_amount, total_amount
""").aggregate("""
VendorID,
DATE_TRUNC('hour', tpep_pickup_datetime) AS pickup_datetime,
DATE_TRUNC('hour', tpep_dropoff_datetime) AS dropoff_datetime,
ROUND(AVG(trip_distance), 2) AS trip_distance,
ROUND(AVG(fare_amount), 2) AS fare_amount,
ROUND(AVG(total_amount), 2) AS total_amount
""").order("""
VendorID, pickup_datetime desc, dropoff_datetime asc
""").limit(10)
결과는
여기에 trip_distance 가 길수록 수익성이 좋을까?
아니면 짧을 수록 수익성이 좋을까?
nyc_table.filter("""
fare_amount > 0 and trip_distance > 0
"""
).project("""
VendorID, tpep_pickup_datetime, tpep_dropoff_datetime, trip_distance, fare_amount, total_amount,
total_amount / trip_distance AS amt_per_dist
""").aggregate("""
VendorID,
DATE_TRUNC('hour', tpep_pickup_datetime) AS pickup_datetime,
DATE_TRUNC('hour', tpep_dropoff_datetime) AS dropoff_datetime,
ROUND(AVG(trip_distance), 2) AS trip_distance,
ROUND(AVG(fare_amount), 2) AS fare_amount,
ROUND(AVG(total_amount), 2) AS total_amount,
ROUND(AVG(amt_per_dist), 2) AS amt_per_dist,
count(*) AS cnt
""").filter("""
cnt > 10
""").order("""
trip_distance desc
""").limit(10)
우선 결과 데이터를 보면
amt_per_dist, cnt 컬럼이 생겼는데
이렇게만 보아서는 잘 모르겠고...
그래프로 보면 좋겠다.
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
sns.scatterplot(data=res, x='trip_distance', y='amt_per_dist', hue='VendorID', style='VendorID', s=50)
plt.title('Trip Distance vs Amount per Distance')
plt.xlabel('Trip Distance')
plt.ylabel('Amount per Distance')
plt.legend(title='NYC')
plt.show()
참고로 데이터 볼륨이 어느정도 될 땐 라인 그래프를 사용하면 컴퓨터가 멈출 수 있으니까 Scatter 를 사용하는 것을 추천한다.
일단 확인을 했는데...
뭔가 계산을 잘못한건지,
데이터 아웃라이어가 빈번해서인지...
앞부분만 보자면 운행거리가 짧을수록 수익성이 약간은 상승하는 느낌이다.
중요한건 관계형 API 를 사용하고 이러한 방식의 확장성이 좋다고 느껴졌다.