A. 목적
택시기사들에게 전달할 파일로, 코로나와 같은 팬데믹 상황이 왔을 때 대비할 수 있게 정보를 전달하고자 한다
상세: 2019년부터 2024년까지 매년 12월 데이터를 통해 뉴욕 옐로우 택시 이용 패턴을 확인하여 택시 기사들의 수익 극대화를 도모한다.
B. 데이터 선택
뉴욕은 미국의 최대 도시이자 가장 유명한 관광지이다. 뉴욕의 슬로건이 ‘The City that Never Sleeps’ 절대 잠들지 않는 도시인 것만 보아도 한눈에 알 수 있다. 어떤 계절이건 늘 관광객으로 가득하다. 특히 12월의 뉴욕은 크리스마스를 보내는 사람들, 새해 카운트를 세고자 하는 사람들로 가득찬다. 특정 기간, 시간에 많은 사람들이 모이는 곳에서의 택시 운행 행태를 파악하기 위하여 2019년부터 2023년까지 5개년치의 12월 데이터를 선택하였다.
세부사항: NYC 에서 제공하는 택세 데이터 중 예약없이 길거리에서 잡아 탑승할 수 있는 옐로우 택시 데이터를 기준으로 분석하고자 한다
C. 데이터 전처리
기초 통계량 및 각 데이터 별 수치를 살펴보고 이상치, 결측치 값 처리 방법을 정하였다.
기준: 각 년도 12월 데이터가 아닌 값, 승객 및 여행 정보에서 딕셔너리에 기재되어 있지 않은 값 처리, 결제 및 요금의 음수값 처리 및 이에 따른 총 결제액 재계산. 등등
1. 1차 전처리 코드
import pandas as pd
# 파일 경로 리스트
file_paths = [
'yellow_tripdata_2019-12.parquet',
'yellow_tripdata_2020-12.parquet',
'yellow_tripdata_2021-12.parquet',
'yellow_tripdata_2022-12.parquet',
'yellow_tripdata_2023-12.parquet'
]
# 파일명에 해당하는 연도 리스트
years = [2019, 2020, 2021, 2022, 2023]
# 전처리 함수 정의
def preprocess_and_save(df, year):
# 2023년 파일일 경우 'Airport_fee' 컬럼명을 'airport_fee'로 변경
if year == 2023:
df.rename(columns={'Airport_fee': 'airport_fee'}, inplace=True)
# 컬럼 타입 변경
df['tpep_pickup_datetime'] = pd.to_datetime(df['tpep_pickup_datetime'])
df['tpep_dropoff_datetime'] = pd.to_datetime(df['tpep_dropoff_datetime'])
# 연도 필터링: tpep_pickup_datetime과 tpep_dropoff_datetime 모두 해당 연도에 맞는 데이터만 남기기
df = df[(df['tpep_pickup_datetime'].dt.year == year) & (df['tpep_dropoff_datetime'].dt.year == year)]
# 결측치 처리
df = df.dropna(subset=['passenger_count', 'RatecodeID', 'store_and_fwd_flag', 'congestion_surcharge'])
df = df[df['payment_type'] != 0]
# 승객 및 여행 정보 이상치 전처리
df = df[df['VendorID'] != 5]
df = df[df['tpep_pickup_datetime'].dt.month == 12]
df = df[df['tpep_dropoff_datetime'].dt.month == 12]
df['passenger_count'] = df['passenger_count'].apply(lambda x: 4 if x >= 5 else x)
df = df[df['trip_distance'] >= 0]
# 결제 및 요금 정보 전처리
df = df[df['payment_type'].isin([1, 2])]
df = df[df['total_amount'] > 0]
df['mta_tax'] = 0.5
df['improvement_surcharge'] = 0.3
df['congestion_surcharge'] = df['congestion_surcharge'].apply(lambda x: 2.5 if x == -2.5 else x)
df = df[df['fare_amount'] > 0]
df['extra'] = df['extra'].abs()
# 승객수 0명인 경우 평균값 2명으로 대치
df['passenger_count'] = df['passenger_count'].replace(0, 2)
# RatecodeID 값이 6 이하인 것만 유지
df = df[df['RatecodeID'] <= 6]
# tip_amount: 0 이상인 값만 유지
df = df[df['tip_amount'] >= 0]
# tolls_amount: 0 이상인 값만 유지
df = df[df['tolls_amount'] >= 0]
# total_amount 값 변환
df['total_amount'] = df['fare_amount'] + df['extra'] + df['mta_tax'] + df['improvement_surcharge'] + df['tolls_amount'] + df['congestion_surcharge'] + df['airport_fee'].fillna(0)
# CSV 파일로 저장
csv_file_name = f'D:/Bootcamp/3rd_project/yellow_taxi_data_{year}.csv'
df.to_csv(csv_file_name, index=False)
# 저장 완료 메시지 출력
print(f"CSV 파일 저장 완료: '{csv_file_name}'")
# 모든 파일에 대해 전처리 및 저장 실행
for file_path, year in zip(file_paths, years):
df = pd.read_parquet(file_path)
preprocess_and_save(df, year)
2. 문제 사항 발견
1) trip distance 값과 total amount 값에 처리되지 않은 이상치 발견
2) Trip distance 값, Total amount 값에서 극단값 발견
위치 정보 전처리 방법 가장 멀리 있는 레코드 두개의 거리 측정 후 그 거리 이상의 값 삭제 처
해당 방법으로 삭제 처리하기 위하여 NYC에서 제공하는 Yellow Taxi Zone 데이터 활용
3) 2차 전처리 코드
import pandas as pd
from shapely import wkt
from shapely.geometry import MultiPolygon
# 데이터 읽기
map_ny2 = pd.read_csv('taxi_zones.csv')
# MULTIPOLYGON에서 경도와 위도를 추출하는 함수
def extract_centroid(geom):
try:
# MULTIPOLYGON 형식의 문자열을 Shapely 객체로 변환
polygon = wkt.loads(geom)
# 다각형의 중심점(centroid) 계산
centroid = polygon.centroid
return centroid.y, centroid.x # 위도, 경도 반환
except Exception as e:
return None, None # 에러 시 None 반환
# 위도와 경도를 추출하여 새로운 열에 추가
map_ny2['Latitude'], map_ny2['Longitude'] = zip(*map_ny2['the_geom'].apply(extract_centroid))
# 경도와 위도가 있는 행만 필터링
map_ny2 = map_ny2.dropna(subset=['Latitude', 'Longitude'])
# 결과 CSV 저장
map_ny2[['LocationID', 'Latitude', 'Longitude']].to_csv('taxi_zones_latlong.csv', index=False)
print("변환이 완료되었습니다. 결과는 taxi_zones_latlong.csv에 저장되었습니다.")
4) 지리적 극단값 처리 코드
기존 데이터와 zone의 위도,경도 데이터를 병합한 후 승차 위치를 기준으로 지리적 거리를 측정하였다.
# LocationID와 위도/경도 정보가 들어있는 location_df 로드
location_df = pd.read_csv('taxi_zones_latlong.csv') # LocationID, Latitude, Longitude가 포함된 데이터
# PULocationID를 기준으로 위도와 경도를 병합 (픽업 지점)
df = df.merge(location_df[['LocationID', 'Latitude', 'Longitude']], left_on='PULocationID', right_on='LocationID', how='left')
df.rename(columns={'Latitude': 'PULatitude', 'Longitude': 'PULongitude'}, inplace=True)
df.drop(columns=['LocationID'], inplace=True)
# DOLocationID를 기준으로 위도와 경도를 병합 (드롭오프 지점)
df = df.merge(location_df[['LocationID', 'Latitude', 'Longitude']], left_on='DOLocationID', right_on='LocationID', how='left')
df.rename(columns={'Latitude': 'DOLatitude', 'Longitude': 'DOLongitude'}, inplace=True)
df.drop(columns=['LocationID'], inplace=True)
# 병합 후 NaN 값이 있는지 확인
print(df.isnull().sum())
# NaN 값이 있는지 확인하고 제거
df_cleaned = df.dropna(subset=['PULatitude', 'PULongitude', 'DOLatitude', 'DOLongitude'])
from geopy.distance import geodesic
# 지리적 거리를 계산하는 함수 정의 (마일 단위로 계산)
def calculate_distance_miles(pickup_coords, dropoff_coords):
return geodesic(pickup_coords, dropoff_coords).miles # 마일 단위로 계산
# 픽업과 드롭오프 지점의 좌표로부터 거리 계산
df_cleaned['distance_miles'] = df_cleaned.apply(
lambda row: calculate_distance_miles((row['PULatitude'], row['PULongitude']),
(row['DOLatitude'], row['DOLongitude'])), axis=1)
# 가장 멀리 떨어진 두 지점 찾기
max_distance_record = df_cleaned.loc[df_cleaned['distance_miles'].idxmax()]
max_distance = max_distance_record['distance_miles']
print(f"가장 멀리 있는 두 지점 사이의 거리: {max_distance} miles")
# 그 거리 이상인 레코드를 필터링해서 제거
df_filtered = df_cleaned[df_cleaned['distance_miles'] <= max_distance]
print(f"필터링 후 남은 레코드 수: {len(df_filtered)}")
df_filtered.to_csv("df_filtered_distance.csv")
그러나 1시간이 지나도 해당 결과를 보지 못하였고, 반복해서 MemoryError가 발생하였다.
이에 맨해튼 거리방법으로 해당 극단값을 해결해보고자 하였다.
5) 맨해튼 거리
# 맨해튼 거리 계산
df['manhattan_distance'] = abs(df['Latitude_pickup'] - df['Latitude_dropoff']) + abs(df['Longitude_pickup'] - df['Longitude_dropoff'])
# 최대 거리 설정
max_distance = df['manhattan_distance'].max()
# 맨해튼 거리 이상인 레코드를 필터링하여 제거
df_filtered = df[df['manhattan_distance'] <= max_distance]
df = df_filtered.copy()
그러나 해당 방법으로 값을 제거하여도 여전히 극단값이 존재하였다.
이에 10만 마일 이상인 값을 제거하는 방법으로 진행하였다.
6) total amount 극단값 처리
해당 total amount 열의 극단값은 가능할 수 있는 값이라 보고 남겨두기로 했다.
7) 택시 구역의 위도, 경도가 포함되지 않은 LocationID를 파악한 후 삭제 처리
# 택시 구역의 위도, 경도 및 LocationID가 포함된 CSV 파일 로드
taxi_zones_df = pd.read_csv('taxi_zones_latlong.csv')
# taxi_zones_df의 Latitude, Longitude 복제
taxi_zones_df['Latitude_copy'] = taxi_zones_df['Latitude']
taxi_zones_df['Longitude_copy'] = taxi_zones_df['Longitude']
# pickup location
df = df.merge(
taxi_zones_df[['LocationID', 'Latitude_copy', 'Longitude_copy']]
.rename(columns={'Latitude_copy': 'pickup_latitude', 'Longitude_copy': 'pickup_longitude'}),
how='left',
left_on='PULocationID',
right_on='LocationID'
)
# dropoff location
df = df.merge(
taxi_zones_df[['LocationID', 'Latitude_copy', 'Longitude_copy']]
.rename(columns={'Latitude_copy': 'dropoff_latitude', 'Longitude_copy': 'dropoff_longitude'}),
how='left',
left_on='DOLocationID',
right_on='LocationID'
)
df = df.merge(
taxi_zones_df[['LocationID', 'Latitude', 'Longitude']],
how='left',
left_on='DOLocationID',
right_on='LocationID'
)
df = df.drop(columns=['LocationID_x', 'LocationID_y'])
print(df.columns)
8) 결측치의 LocationID 확인
# PULocationID가 location_df에 없는 경우 확인
pulocation_missing = df[~df['PULocationID'].isin(location_df['LocationID'])]
# DOLocationID가 location_df에 없는 경우 확인
dolocation_missing = df[~df['DOLocationID'].isin(location_df['LocationID'])]
# 결과 출력
print(f"LocationID가 없는 PULocationID 레코드 수: {len(pulocation_missing)}")
print(f"LocationID가 없는 DOLocationID 레코드 수: {len(dolocation_missing)}")
# PULocationID가 location_df에 없는 고유 값 확인
missing_pulocation_ids = pulocation_missing['PULocationID'].unique()
print(f"LocationID가 없는 PULocationID 목록: {missing_pulocation_ids}")
# DOLocationID가 location_df에 없는 고유 값 확인
missing_dolocation_ids = dolocation_missing['DOLocationID'].unique()
print(f"LocationID가 없는 DOLocationID 목록: {missing_dolocation_ids}")
# 제외할 LocationID 목록
exclude_locations = [264, 265, 57, 105]
# PULocationID와 DOLocationID에서 제외하는 필터링 조건 적용
df = df[~df['PULocationID'].isin(exclude_locations) & ~df['DOLocationID'].isin(exclude_locations)]
LocationID를 살펴보고 승차 및 하차 위치 코드에 없는 번호를 찾아 삭제 처리하였다.
C. 분석 모형
- 목적: 택시기사에게 최대 수익을 가져다 줄 수 있는 승객의 탑승 위치를 예측하고자 한다.
- 기본 형식 지정
df = pd.read_csv('yellow_taxi_data_mega_df.csv')
# 컬럼 타입 변경
df['tpep_pickup_datetime'] = pd.to_datetime(df['tpep_pickup_datetime'])
df['tpep_dropoff_datetime'] = pd.to_datetime(df['tpep_dropoff_datetime'])
# 'pickup_day' 열을 새로 생성 (요일 정보 추출)
df['pickup_day'] = df['tpep_pickup_datetime'].dt.dayofweek # 0: 월요일, 6: 일요일
df['hour'] = df['tpep_pickup_datetime'].dt.hour
1. KMeans를 활용한 분석
- 모델 사용 이유: 승차 위치 그룹화를 통해 효율적인 운영 전략을 도출하기 위해 최적의 승차 위차를 찾고자 함.(위치 기반 시스템을 개선하고자 함)
1) KMeans 코드
# 위도와 경도를 이용한 클러스터링
locations = df[['Latitude', 'Longitude']].dropna().drop_duplicates()
# KMeans 클러스터링 적용
kmeans = KMeans(n_clusters=10, random_state=42).fit(locations)
# 클러스터링 대상이 된 데이터프레임의 인덱스 추출 (dropna를 고려한)
valid_indices = df[['Latitude', 'Longitude']].dropna().index
# 각 승차 위치에 클러스터 할당 (dropna된 행에 대해 클러스터 예측)
df.loc[valid_indices, 'cluster'] = kmeans.predict(df.loc[valid_indices, ['Latitude', 'Longitude']])
# 특정 시간대와 요일에 대해 클러스터별 평균 수익 계산
filtered_data = df[(df['hour'] == hour_input) & (df['pickup_day'] == day_input)]
# NaN 값을 제거한 후, 클러스터별 평균 수익을 계산
cluster_revenues = filtered_data.groupby('cluster')['total_amount'].mean().reset_index()
# 수익이 높은 순으로 클러스터 정렬
cluster_recommendations = cluster_revenues.sort_values(by='total_amount', ascending=False)
# 상위 3개의 클러스터 추천
top_clusters = cluster_recommendations.head(3)
print("추천 클러스터:", top_clusters)
# 해당 클러스터의 대표 승차 위치 추천
for cluster in top_clusters['cluster']:
location_in_cluster = df[df['cluster'] == cluster][['PULocationID', 'Latitude', 'Longitude']].drop_duplicates().head(5)
print(f"클러스터 {cluster}의 추천 승차 위치:")
print(location_in_cluster)
추천 클러스터: cluster total_amount
2 2.0 80.937500
7 7.0 56.521240
9 9.0 50.388571
클러스터 2.0의 추천 승차 위치:
PULocationID Latitude Longitude
23055 1 40.691831 -74.174000
33390 5 40.552659 -74.188484
65674 44 40.525495 -74.233534
65675 23 40.606448 -74.170887
224371 118 40.586555 -74.132979
클러스터 7.0의 추천 승차 위치:
PULocationID Latitude Longitude
13 132 40.646985 -73.786533
1488 10 40.678953 -73.790986
3006 180 40.675595 -73.847043
6016 216 40.676154 -73.819460
7382 219 40.662185 -73.764506
클러스터 9.0의 추천 승차 위치:
PULocationID Latitude Longitude
18423 6 40.600324 -74.071771
19967 11 40.604273 -74.007488
30813 14 40.624835 -74.029892
49596 221 40.618769 -74.073704
103323 67 40.619619 -74.013801
2) 클러스터링 시각화 코드 및 결과
# 클러스터링 결과 시각화
plt.figure(figsize=(10, 8))
# 클러스터별로 색상 구분하여 시각화
plt.scatter(df['Longitude'], df['Latitude'], c=df['cluster'], cmap='viridis', alpha=0.5)
# 클러스터의 중심점도 시각화
centers = kmeans.cluster_centers_
plt.scatter(centers[:, 1], centers[:, 0], c='red', s=200, alpha=0.75, marker='x')
plt.title('Taxi Pickup Location Clusters')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.show()
2. Elbow Method 를 활용한 최적의 클러스터 수 및 군집 분석
# 산점도: 위도와 경도를 기준으로 클러스터에 따라 색을 다르게 표현
sns.scatterplot(
x='Longitude',
y='Latitude',
hue='location_cluster', # 클러스터에 따라 색상을 다르게 설정
data=filtered_data, # 데이터프레임을 random_sample_100k로 수정
palette='viridis',
legend='full'
)
# 클러스터 중심 계산
centroids = filtered_data.groupby('location_cluster')[['Latitude', 'Longitude']].mean().reset_index()
# 클러스터 중심에 마커 추가
plt.scatter(
centroids['Longitude'],
centroids['Latitude'],
s=100, # 마커 크기
c='red', # 마커 색상
label='Centroids', # 범례 이름
marker='X' # 마커 형태 (X 모양으로 표시)
)
# 그래프 설정
plt.title('Cluster Visualization of Taxi Pickups')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.legend(title='Cluster')
# 그래프 출력
plt.show()
1. Cluster 0 (보라색):
- 위치: 대부분의 데이터 포인트가 뉴욕의 서쪽에 위치해 있다. 주로 로어 맨해튼 또는 허드슨 강 근처일 가능성이 있다.
- 특성: 이 클러스터는 다른 클러스터들보다 좀 더 넓은 범위에 퍼져있으며, 도시 외곽에 더 가깝다.
2. Cluster 1 (파란색):
- 위치: 뉴욕 시 맨해튼의 중심부 근처에 집중되어 있다.
- 특성: 이 클러스터는 맨해튼 북부 또는 중부 지역의 택시 픽업 위치를 나타낼 수 있다. 도시 내에서 더 밀집된 픽업 패턴을 보여주고 있다.
3. Cluster 2 (녹색):
- 위치: 뉴욕 시의 동쪽, 특히 퀸즈나 브루클린으로 추정되는 지역에서 주로 발생하는 픽업을 나타낸다.
- 특성: 이 군집은 도시 중심에서 떨어진 곳에서 주로 형성된 클러스터이다.
4. Cluster 3 (노란색):
- 위치: 맨해튼 남부와 허드슨 강에 가까운 지역.
- 특성: 맨해튼의 하단부에서 매우 밀집된 픽업 위치가 많으며, 뉴욕의 핵심 중심지에 있는 클러스터일 가능성이 크다.
5. 군집 특성 분석:
- 지리적 범위: 각 클러스터가 나타내는 위치에 따라 주요 택시 픽업 지역의 밀집도를 보여준다. Cluster 0과 Cluster 2는 도시 외곽에서 픽업되는 빈도가 높은 반면, Cluster 1과 3은 맨해튼의 중심부에서 발생하는 픽업이다.
- 클러스터 중심: 각 클러스터의 중심은 군집의 평균적인 픽업 위치를 나타내며, 픽업 활동이 활발한 지역을 확인하는 데 유용하다.
3. Random Forast Regressor 를 활용한 분석
- 모델 사용 이유: 여러 개의 의사결정트리를 사용해 과적합 방지 및 예측 성능을 향상하기 위해 사용함.
- 회귀분석: 운행 거리와 총 수익간의 관계를 분석하고 회귀 직선을 시각화 하기 위해 진행함.
1) 머신러닝 모델 코드
# 모델에 사용할 특성
features = random_sample_100k[['pickup_day_of_week', 'pickup_hour', 'location_cluster', 'manhattan_distance', 'total_amount']]
target = random_sample_100k['RatecodeID']
# 데이터를 학습용과 테스트용으로 분할
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
# 랜덤 포레스트 모델로 학습
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
# 테스트 데이터를 통해 예측
y_pred = rf_model.predict(X_test)
# 모델 성능 평가 (RMSE)
mse = mean_squared_error(y_test, y_pred)
rmse = mse ** 0.5
print(f'평균 제곱근 오차(RMSE): {rmse:.2f}')
# 평균 제곱근 오차(RMSE): 13.81
- Accuracy 0.99인 값이 측정되었다.
2) 실제값과 예측값의 산점도
수치가 작은 값들은 잘 맞지만, 적절한 모델은 아닌 것으로 보인다.
4. LightGBM 사용하여 재분석
# 클러스터링을 위한 위도, 경도 기반 KMeans 적용
kmeans = KMeans(n_clusters=4, random_state=42) # 클러스터 수는 4로 설정
df['location_cluster'] = kmeans.fit_predict(df[['Latitude', 'Longitude']])
# 추가 피처로 수익성 관련 칼럼 선택
X = df[['pickup_day_of_week', 'pickup_hour','pickup_latitude','pickup_longitude','dropoff_latitude','dropoff_longitude']]
y = df['total_amount']
# 데이터 분리 (학습 데이터와 테스트 데이터로 나누기)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 훈련 및 테스트 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# LightGBM 회귀 모델 정의
lgb_model = lgb.LGBMRegressor(
n_estimators=1000,
learning_rate=0.05,
max_depth=10,
random_state=42,
n_jobs=-1
)
# 모델 학습 - early stopping 및 학습 진행 상황 출력
lgb_model.fit(
X_train, y_train,
eval_set=[(X_test, y_test)],
eval_metric='rmse', # 평가 메트릭 설정
callbacks=[
lgb.early_stopping(stopping_rounds=100), # early stopping 콜백
lgb.log_evaluation(50) # 50번마다 학습 로그 출력
]
)
# 예측
y_pred = lgb_model.predict(X_test)
# 모델 성능 평가 (RMSE)
rmse = mean_squared_error(y_test, y_pred, squared=False)
print(f"RMSE: {rmse}")
# 예측값이 연속형이므로 회귀 평가지표 사용
print("RMSE:", mean_squared_error(y_test, y_pred, squared=False)) # Root Mean Squared Error
print("MAE:", mean_absolute_error(y_test, y_pred)) # Mean Absolute Error
print("R² Score:", r2_score(y_test, y_pred)) # R^2 Score (결정 계수)
# RMSE: 6.15423934688325
# MAE: 3.534872188639418
# R² Score: 0.8479843617006366
![]() |
![]() |
![]() |
5. 승하차 지역 중 가장 많은 지역 Top20
1) Top 20 코드
DOtemp = df['DOLocationID'].value_counts().head(20).reset_index()
PUtemp = df['PULocationID'].value_counts().head(20).reset_index()
DOtemp = DOtemp.rename(columns={'DOLocationID': 'LocationID'})
PUtemp = PUtemp.rename(columns={'PULocationID': 'LocationID'})
data = pd.merge(DOtemp['LocationID'],PUtemp['LocationID'],on='LocationID', how='outer')
2) Top 20 시각화
nyc_map = folium.Map(location=[40.7128, -74.0060], zoom_start=12)
for index, row in top_locations_with_coords.iterrows():
folium.Marker([row['Latitude'], row['Longitude']],
popup=f"Location ID: {row['LocationID']}").add_to(nyc_map)
- 해당 위치와 클러스터의 중심이 비슷한 것으로 보아 Top 20 곳 혹은 Top10 위치로 범위를 좁혀 진행하였다.
4. 승하차 지역 중 가장 많은 지역 Top10 데이터를 활용한 머신러닝
1) 데이터 준비
# Top10 안에 들어가는 값만 추출
filtered_data = df[(df['PULocationID'].isin(top_locations_with_coords['LocationID'])) |
(df['DOLocationID'].isin(top_locations_with_coords['LocationID']))]
filtered_data.describe()
2) 클러스터링을 위한 위도, 경도 기반 KMeans 적용 및 피쳐, 타겟 변수 변경
# 클러스터링을 위한 위도, 경도 기반 KMeans 적용
kmeans = KMeans(n_clusters=4, random_state=42) # 클러스터 수는 4로 설정
df['location_cluster'] = kmeans.fit_predict(df[['Latitude', 'Longitude']])
# 추가 피처로 수익성 관련 칼럼 선택
X = df[['pickup_day_of_week', 'pickup_hour','pickup_latitude','pickup_longitude','dropoff_latitude','dropoff_longitude']]
y = df['total_amount']
# 훈련 및 테스트 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# LightGBM 회귀 모델 정의
lgb_model = lgb.LGBMRegressor(
n_estimators=1000,
learning_rate=0.05,
max_depth=10,
random_state=42,
n_jobs=-1
)
# 모델 학습 - early stopping 및 학습 진행 상황 출력
lgb_model.fit(
X_train, y_train,
eval_set=[(X_test, y_test)],
eval_metric='rmse', # 평가 메트릭 설정
callbacks=[
lgb.early_stopping(stopping_rounds=100), # early stopping 콜백
lgb.log_evaluation(50) # 50번마다 학습 로그 출력
]
)
# 예측
y_pred = lgb_model.predict(X_test)
# 모델 성능 평가 (RMSE)
rmse = mean_squared_error(y_test, y_pred, squared=False)
print(f"RMSE: {rmse}")
# 예측값이 연속형이므로 회귀 평가지표 사용
print("RMSE:", mean_squared_error(y_test, y_pred, squared=False)) # Root Mean Squared Error
print("MAE:", mean_absolute_error(y_test, y_pred)) # Mean Absolute Error
print("R² Score:", r2_score(y_test, y_pred)) # R^2 Score (결정 계수)
결과는 아래와 같다.
3) 실제값과 예측 값 산점도를 그려본다면 아래와 같은 결과가 나타난다.
![]() |
![]() |
![]() |
- 실제값과 예측값 간의 산점도를 기본적으로 한 번 보고, 로그변환을 하여 보고, 축범위를 제한하여 보았다. 대부분의 그래프에서 낮은 값에 대해 높은 정확도를 보여주고 있다.
D. 마무리
모델의 초기 분석에서는 충분한 인사이트를 도출하지 못했지만, 이를 통해 추가적인 분석 방향을 설정할 수 있었다. 특히 클러스터링을 통해 도출한 추천 승차 위치와 위도·경도 정보를 보면, 이 값들이 프로젝트의 목표 달성에 중요한 역할을 할 수 있었을 것이라는 생각이 든다. 그러나 분석 과정에서 데이터를 충분히 이해하지 못한 채 기계적으로 분석한 결과, 프로젝트를 중도에 마무리하게 된 것 같다.
앞으로는 고객의 하차 지점에서 다음 고객의 목적지를 예측해, 더 높은 수익을 낼 수 있는 고객을 어떻게 태울 수 있을지에 대한 분석을 진행하고자 한다. 택시 운행은 하나의 사이클로 볼 수 있다. 손님을 내려준 후 빈 차로 돌아오는 상황을 줄이지 않으면 수익에 손해가 발생한다. 따라서 빈 차로 도로를 달리는 시간을 최소화하고, 더 높은 수익을 낼 수 있는 고객의 목적지를 예측하는 모델을 구축하고 싶다.
이를 위해 고객의 목적지와 다음 승객의 예상 위치를 함께 고려한 최적 경로 예측 모델을 탐색하여 택시 기사의 운영 효율성을 향상시키는 방안을 마련할 계획이다.
'Project' 카테고리의 다른 글
[4차 프로젝트] 식중독 발생 확률 예측 및 대시보드 생성 (1) | 2024.11.07 |
---|---|
[2차 프로젝트] 전자상거래 데이터분석 및 마케팅 전략 제시 (0) | 2024.09.09 |
[1차 프로젝트] 데이터 시각화 및 인사이트 도출 (4) | 2024.08.12 |
[1차 프로젝트] 데이터 전처리 (0) | 2024.07.31 |
[1차 프로젝트] 데이터 이해하기 (0) | 2024.07.30 |