
NBA 공홈 기록 기반 대규모(7GB) 플레이 바이 플레이 데이터.
오늘 최종적으로 정리된 파이프라인은 아래 순서대로 이뤄졌다.
import pandas as pd
import numpy as np
print("전체 프로세스 시작")
# 1. 파일 로드
try:
df_games = pd.read_csv('games.csv')
df_details = pd.read_csv('games_details.csv')
df_players = pd.read_csv('players.csv')
df_teams = pd.read_csv('teams.csv') # ranking은 안 씀
print("파일 로드 성공")
except FileNotFoundError:
print("실패! 파일 경로 확인.")
■ 과정 & 문제
SEASON, GAME_DATE_EST, HOME_TEAM_ID, VISITOR_TEAM_ID 붙이기merged_df = pd.merge(
df_details,
df_games[['GAME_ID', 'SEASON', 'GAME_DATE_EST', 'HOME_TEAM_ID', 'VISITOR_TEAM_ID']],
on='GAME_ID',
how='left'
)
print(f"1차 병합 완료 (행 개수: {len(merged_df)})")
병합 후 형태:
(3,218,444 rows, 29 columns) → (3,218,444 rows, 34 columns)
❗ 느낀 점
■ 핵심 포인트
가장 오래 걸렸던 부분이다..
병합 후 컬럼 상태:
PLAYER_NAME_x
PLAYER_NAME_y
둘 다 PLAYER_NAME인데 Whyrano,,,,
→ games_details는 경기 시점 이름, players는 DB 기반 공식 이름.
그래서 suffix 처리 후 최신 이름으로 통일.
merged_df = pd.merge(
merged_df,
df_players[['PLAYER_ID', 'PLAYER_NAME']],
on='PLAYER_ID',
how='left',
suffixes=('_OLD', '_NEW')
)
# PLAYER_NAME_NEW가 진짜 최신 컬럼
if 'PLAYER_NAME_NEW' in merged_df.columns:
merged_df['PLAYER_NAME'] = merged_df['PLAYER_NAME_NEW']
merged_df.drop(columns=['PLAYER_NAME_NEW', 'PLAYER_NAME_OLD'], errors='ignore', inplace=True)
print("선수 정보 병합 완료")
🐞 NameError / KeyError 관련 문제 발생했던 구간
갯수는 맞는데, 중복 컬럼이 6개씩 튀어나옴..
원인: games_details 안에도 TEAM_ID가 있고,
teams 안에도 TEAM_ID가 있어서 모든 조합이 싹 다 생성됨.
수동 정리 해야 함 ㅋㅋㅠㅠㅠㅠ
merged_df = pd.merge(
merged_df,
df_teams[['TEAM_ID', 'ABBREVIATION', 'CITY']],
on='TEAM_ID',
how='left',
suffixes=('', '_TEAM_INFO')
)
merged_df = merged_df.rename(columns={
'ABBREVIATION': 'TEAM_ABBREVIATION',
'CITY': 'TEAM_CITY'
})
print("팀 정보 병합 완료")
🐞 KeyError: TEAM_ID
원인:
🐞 TEAM_CITY / TEAM_ABBR 중복 4개 생성
# 중복 컬럼 인덱스 처리
clean_cols = merged_df.columns[~merged_df.columns.duplicated(keep=False)].tolist()
cols = merged_df.columns
abbr_locs = [i for i, col in enumerate(cols) if col == 'TEAM_ABBREVIATION']
city_locs = [i for i, col in enumerate(cols) if col == 'TEAM_CITY']
merged_df_final = merged_df[clean_cols].copy()
merged_df_final['TEAM_ABBREVIATION'] = merged_df.iloc[:, abbr_locs[-1]]
merged_df_final['TEAM_CITY'] = merged_df.iloc[:, city_locs[-1]]
merged_df = merged_df_final
print("중복 컬럼 정리 완료")
🐞 가장 시간을 많이 잡아먹은 지옥 구간
def convert_min_to_float(min_str):
if pd.isna(min_str):
return np.nan
min_str = str(min_str).strip()
if min_str in ['', '--', '-', '—']:
return np.nan
if ':' in min_str:
try:
m, s = min_str.split(':')
return int(m) + int(s) / 60
except:
return np.nan
try:
return float(min_str)
except:
return np.nan
merged['MIN'] = merged['MIN'].apply(convert_min_to_float)
실제로 터진 에러 로그
ValueError: invalid literal for int() with base 10: '—'
ValueError: could not convert string to float: '0:00'
TypeError: argument of type 'float' is not iterable
이거 해결하느라 시간 많이 쏟음 ㅎㅎ,,
numerical_cols = [
'MIN','FGM','FGA','FG_PCT','FG3M','FG3A','FG3_PCT','FTM','FTA','FT_PCT',
'OREB','DREB','REB','AST','STL','BLK','TO','PF','PTS','PLUS_MINUS'
]
merged_df[numerical_cols] = merged_df[numerical_cols].fillna(0)
categorical_cols = [
'PLAYER_NAME','TEAM_ABBREVIATION','TEAM_CITY','NICKNAME'
]
for col in [c for c in categorical_cols if c in merged_df.columns]:
merged_df[col] = merged_df[col].fillna('UNKNOWN')
print("결측치 처리 완료")
merged_df['GAME_DATE_EST'] = pd.to_datetime(merged_df['GAME_DATE_EST'])
print("GAME_DATE_EST 타입:", merged_df['GAME_DATE_EST'].dtype)
print("날짜 형식 변환 완료")
9-1. PLUS_MINUS 결측 제거
merged_df.dropna(subset=['PLUS_MINUS'], inplace=True)
9-2. MIN < 0 제거
merged_df = merged_df[merged_df['MIN'] >= 0].copy()
9-3. 유효 기록 기준 필터링
merged_df = merged_df[
(merged_df['MIN'] > 0) &
((merged_df['FGA'] > 0) | (merged_df['FTA'] > 0) | (merged_df['REB'] > 0))
].copy()
print("유효 기록 필터링 완료")
필터 전: 3,218,444
필터 후: 2,792,310
약 42만개 행 제거
10-1. PPM
merged_df['PPM'] = merged_df['PTS'] / merged_df['MIN']
print("PPM 생성 완료")
10-2. SEI 종합 효율 지표
수식:
**SEI = (PTS + REB + AST + STL + BLK) − (TO + (FGA − FGM))**
merged_df['SEI'] = (
merged_df['PTS'] + merged_df['REB'] + merged_df['AST'] +
merged_df['STL'] + merged_df['BLK']
) - (
merged_df['TO'] + (merged_df['FGA'] - merged_df['FGM'])
)
merged_df['SEI_Per_Minute'] = merged_df['SEI'] / merged_df['MIN']
print("SEI 및 분당 효율 생성 완료")
player_sei_stats = merged_df.groupby('PLAYER_NAME').agg(
total_minutes=('MIN','sum'),
avg_sei_per_min=('SEI_Per_Minute','mean'),
total_pts=('PTS','sum')
)
top_sei_players = player_sei_stats[player_sei_stats['total_minutes'] >= 1000]
top_sei_players = top_sei_players.sort_values(by='avg_sei_per_min', ascending=False)
print(top_sei_players.head(10))
| PLAYER_NAME | total_minutes | total_pts | avg_sei_per_min |
|---|---|---|---|
| Anthony Bennett | 14059 | 5190 | 1.128 |
| Johnathan Motley | 1429 | 858 | 1.088 |
| Joel Embiid | 77624 | 64020 | 0.967 |
| Nikola Jokic | 98088 | 64155 | 0.935 |
| Mario West | 3018 | 639 | 0.915 |
| Luka Doncic | 22656 | 18030 | 0.889 |
| Zion Williamson | 3917 | 3101 | 0.882 |
| Giannis Antetokounmpo | 182454 | 124915 | 0.880 |
| Anthony Davis | 196311 | 137344 | 0.862 |
| Jeff Withey | 11620 | 3865 | 0.852 |
correlation_cols = [
'PLUS_MINUS','MIN','PTS','REB','AST','STL','BLK','TO',
'FGM','FGA','FG_PCT','FG3M','FG3A','FTM','FTA'
]
corr = merged_df[correlation_cols].corr()
plus_minus_corr = corr['PLUS_MINUS'].sort_values(ascending=False)
print(plus_minus_corr)
주요 결과
home_team_records = merged_df[merged_df['HOME_TEAM_ID'] == merged_df['TEAM_ID']]
win_stats = home_team_records[home_team_records['PLUS_MINUS'] > 0][['PTS','AST','REB','FG_PCT']].mean()
loss_stats = home_team_records[home_team_records['PLUS_MINUS'] <= 0][['PTS','AST','REB','FG_PCT']].mean()
print("승리 시 평균:", win_stats)
print("패배 시 평균:", loss_stats)
🐞 NameError: merged_df is not defined
원인: 런타임 초기화
해결: 단계 분리 실행
🐞 KeyError
원인: suffix 충돌 / 컬럼명 다름
해결: rename + duplicated 인덱스 처리
🐞 SettingWithCopyWarning
해결: .loc 사용
🐞 MIN 변환 시 ValueError
해결: robust 변환 함수 제작
글이고 작곡이고 다 필요 없고 플젝에만 집중하자 생각하고
지금 이 시간까지 TIL 작성하다 잡니다.
진작 좀 정신 차리면 어디 덧나는지,,~
내배캠,, 좋지만, 너무나도 가혹합니다.
