imhamburger 님의 블로그

데이터엔지니어 부트캠프 - 첫번째 팀프로젝트 (8/2~8/6) 2-3일차 본문

데이터엔지니어 부트캠프

데이터엔지니어 부트캠프 - 첫번째 팀프로젝트 (8/2~8/6) 2-3일차

imhamburger 2024. 8. 5. 23:26

3일간의 팀프로젝트가 끝났다.
지난번에 팀프로젝트를 진행하면서 발생한 에러들을 기록해놨었는데 오늘도 2일차에 발생한 에러들을 이어서 정리해보려고 한다.
 
 

제 4 장: 파이썬 함수에 있는 변수는 Bash Command에 적용되지 않는다.

 

우리는 에어플로우가 실행될 때마다 같은 데이터가 누적되어 쌓이는 것이 아닌 멱등성을 고려해 데이터가 존재한다면 삭제한 후 Load하고 없다면 데이터를 삭제하지 않고 Load하는 방식으로 데이터 파이프라인을 설계하였다.
 

 
따라서 branch_fun이라는 함수를 만들어 조건문을 넣었고 BranchOperator에서 명령을 분리해준다.
 

def branch_fun(ds_nodash):
        import os
        home_dir = os.path.expanduser("~")
        month=int(ds_nodash[4:6])
        path = os.path.join(home_dir, f"code/playgogo/storage/month={month}/load_dt={ds_nodash}")
        print('*' * 30)
        print(path)
        print('*' * 30)   

        if os.path.exists(path):
            print('존재')
            return "rm.dir" #rmdir.task_id
        else:
            print('존재x')
            return "load"
            
branch_op = BranchPythonOperator(
            task_id="branch.op",
            python_callable=branch_fun
    )

 
그리고 우리가 처음에 작성하였던 rm.dir Task와 load Task는 다음과 같다.

def fun_load(dt):
        print('*'*30)
        print(dt)
        print('*'*30)

        from load.load import load
        load(load_dt=dt)
        
load = PythonVirtualenvOperator(
            task_id='load',
            python_callable=fun_load,
            trigger_rule="all_done",
            requirements=["git+https://github.com/play-gogo/load.git@d2/0.1.0"],
            system_site_packages=False,
            op_args=["{{ds_nodash}}"]
    )
    
    
rm_dir = BashOperator(
            task_id='rm.dir',
            bash_command="""
                rm -rf ~/code/playgogo/storage/month={month}/load_dt={{ds_nodash}}
            """

    )

 
우리는 이미 branch_fun 함수에 month=int(ds_nodash[4:6]) 이라는 month 변수를 설정하였기 때문에 Task에서 바로 적용이 가능할 줄 알았다. 그러나 에어플로우를 실행하였을 때 데이터가 있을 때 계속 쌓여서 저장되는 현상을 볼 수 있었다. 아래처럼 말이다.

 

처음엔 에러도 없는데 데이터 삭제가 되지 않길래 Task의 Trigger_rule을 잘못 설정한 줄 알았다. 따라서 load의 Trigger_rule을 All_done에서 one_success로 바꾸어보았다. all_done은 앞에 Task에서 모두 수행이 되면 수행되는 조건이며, one_success는 앞 Task들 중 하나라도 성공하면 수행되는 조건이다. 하지만 이 문제가 아니었다. all_done이 맞는 조건이다.
 
고민을 하던 중에 전에 bash command에서 파이썬 실행을 하였었는데 에러가 났던게 생각이 났다.
그래서 코드를 보니 rm.dir에 month={month}가 잘못되었다는 것을 깨달았다. 따라서 코드를 아래처럼 수정하였다.

rm_dir = BashOperator(
            task_id='rm.dir',
            bash_command="""
                month=$(echo "{{ ds_nodash[4:6] }}" | awk '{print $1+0}');
                rm -rf ~/code/playgogo/storage/month=$month/load_dt={{ds_nodash}}
            """

    )

 
month 라는 변수를 다시 bash_command에 주었다.
에어플로우 날짜 Jinja 템플릿을 이용하여 '월'을 슬라이싱하여 추출하는데 awk '{print $1+0}'을 사용하였다.
 
뒤에 +0을 붙인 이유는,
$1 필드를 숫자로 변환하여 출력하기 위해서이다. awk는 기본적으로 문자열로 인식된 데이터를 다루는데, 이 경우 숫자 계산이나 숫자 포맷으로 출력을 하기 위해서는 해당 문자열을 숫자로 변환하는 과정이 필요하다.

$1+0의 역할은 $1을 숫자로 해석하도록 한다. 예를 들어, $1이 "123"이라는 문자열이라면, $1+0을 통해 이 문자열을 숫자 123으로 변환하여 숫자처럼 취급하게 된다.
 
예를들어, 

echo "123abc abc" | awk '{print $1}'

위 코드는 "123abc"를 출력하며 문자열이다.

echo "123abc abc" | awk '{print $1+0}'

위 코드는 출력되는건 123 뿐이다. 게다가 숫자이다. +0이 숫자만 해석하고 숫자로 변환하여 출력하기 때문이다.
 
따라서 날짜 문자열을 숫자형태로 바꾸어 변수로 저장하고 rm- rf 명령을 실행하니 정상적으로 작동하였다.

 
 

제 5 장: 에러는 아니지만 판다스에 익숙치 않아...

 
저장한 데이터를 판다스로 read하여 데이터를 집계하는 과정까지 진행하였다.
그런데 판다스를 쓰는 것에 익숙치 않아 엄청 헤맸다. 판다스에서 제공하는 그래프로 데이터를 시각화하려고 하였는데 그냥 어려웠다...
 
우리가 보고싶었던 데이터는 다음과 같다.

  • 2020년도에 관객수가 가장 많았던 영화
  • 월별로 가장 매출액이 높았던 영화

집계하는데 있어 시간을 많이 썼다. 왜냐하면 협업부서에서 데이터를 볼 때 인사이트를 얻을 수 있어야 하기 때문이다. 필요로 하지 않는 데이터는 당연히 필요가 없기 때문에... 유의미한 데이터를 추출하고 싶었다.
 
만약, 이 데이터를 활용한다면? 영화관 관계자가 이 데이터를 보는 사람이라고 상상해보았다.
 
2020년에 인기 많았던 영화 재개봉
재개봉 일정을 미리 공지하고, 팬들에게 혜택을 제공하는 특별 이벤트를 기획한다. 예를 들어, 관객들을 위해 영화 관련 굿즈나 기념품을 제공하거나, 영화 상영 후 감독과의 Q&A 세션 마련.

시즌별 인기 영화 분석
각 시즌에 어떤 장르의 영화가 인기가 있었는지 분석하고, 그 데이터를 바탕으로 다음 시즌에 적절한 장르의 영화를 상영한다. 예를 들어, 봄에는 로맨스, 여름에는 액션, 가을에는 드라마, 겨울에는 가족 영화 등을 상영하여 관객의 기대를 충족시킬 수 있도록!

코로나19 영향 분석
코로나19로 인한 관객 수 감소나 변화를 비교하기 위해 2020년과 그 전후 년도의 평균 관객 수를 분석 필요. 이를 통해 향후 팬데믹 상황에 대비한 전략을 마련하고, 영화관 운영에 대한 전반적인 대응 방안을 수립.

온라인 스트리밍과의 연계
집에서 영화를 보는 사람들이 증가하는 추세에 맞추어, 영화관 관람과 온라인 스트리밍을 연계한 패키지를 제공한다. 예를 들어, 영화관 티켓과 스트리밍 서비스 구독권을 묶은 패키지를 판매하여 관객의 선택을 넓히기.

회원 제도 및 로열티 프로그램 강화
영화관 방문 및 관람 경험을 통합적으로 관리할 수 있는 회원 제도나 로열티 프로그램을 도입한다. 이를 통해 반복 방문을 유도하고, 충성 고객층을 구축합니다. 예를 들어, 포인트 적립, 할인 혜택, 특별 상영 초대 등 다양한 혜택을 제공.
 
등이 있을 것 같다.
 
하지만 이 데이터에서 아쉬운 점은 영화진흥위원회에서 제공하는 오픈 API 영화데이터에 추가적으로 영화 장르, 관객수가 있으니 평균 연령대가 있었다면 조금 더 구체적인 타겟층을 잡아 인사이트를 얻을 수 있지 않을까 하는 생각이다.
 

  • 2020년도에 관객수가 가장 많았던 영화

판다스 피벗테이블을 이용하면 더 간단하게 표현할 수 있을 것 같아 피벗테이블을 이용하였다.

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_parquet('~/code/playgogo/storage')
df = pd.DataFrame(df)

#피벗테이블 이용
pdf1 = pd.pivot_table(df,                # 피벗할 데이터프레임
                     columns = 'movieNm',    # 열 위치에 들어갈 열
                     values = 'audiCnt',     # 데이터로 사용할 열
                     aggfunc = 'sum')   # 데이터 집계함수

#열과 행 바꾸기
cnt = pdf1.T

#관객수 내림차순으로 하여 10개까지 추출
sort_cnt = cnt.sort_values('audiCnt', ascending=False).head(10)
sort_cnt


결과

 
데이터시각화

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['font.family'] = 'AppleGothic'

plt.figure(figsize=(12, 10))

# 월별로 가장 인기 있는 영화의 인기도를 막대 그래프로 시각화
sns.barplot(data=sort_cnt, x='movieNm', y='audiCnt')

plt.title('2020년 관객수 Top 10 영화', fontsize=15)
plt.xlabel('영화명', fontsize=15)
plt.ylabel('총 관객수', fontsize=15)
plt.xticks(rotation=45,fontsize=15)
plt.yticks(ticks=[0, 2500000, 5000000], fontsize=15)

#y축 눈금 범위
plt.ticklabel_format(style='plain', axis='y')
 
for index, row in sort_cnt.iterrows():
    plt.text(index, row['audiCnt'], f"{row['audiCnt']:,}", color='black', ha="center", fontsize=15)
    
plt.tight_layout()
plt.show()

 
데이터시각화 결과

처음에 y축 눈금범위가 잘 2.5, 5.0 이런식으로 소수점으로 나와 그래프가 이상하게 나왔다.
ticks 값을 주지않아 생기는 문제라 생각했고 ticks=[0, 2500000, 5000000] 으로 설정하였다. 그래도 해결은 되지 않았다.
 
구글링한 결과,
plt.ticklabel_format(style='plain', axis='y') 를 추가해줘야 해결되는 문제였다.
ticklabel_format을 기억해두자!
 

  • 월별로 가장 매출액이 높았던 영화
import pandas as pd

# Parquet 파일을 읽어온다.
df = pd.read_parquet('/home/sujin/code/playgogo/storage')

# 월별 가장 인기 있는 영화 추출
monthly_top_movies = df.groupby(['month','movieNm'])['salesAmt'].sum().reset_index()
idx=monthly_top_movies[monthly_top_movies['salesAmt']!=0].groupby('month')['salesAmt'].idxmax()

# 인덱스를 사용하여 원본 데이터프레임에서 해당 행을 추출
monthly_top_movies = monthly_top_movies.loc[idx]
monthly_top_movies['month']= monthly_top_movies['month'].astype('int')
monthly_top_movies

1. parquet 파일을 읽어온다.
2. 월, 영화명으로 그룹화하고 매출액 'salesAmt'를 sum한다. 그리고 index를 초기화한다.
3. idx = 매출액이 0이 아닌 영화를 월 month로 그룹화하는데 해당 월에 매출액이 최대값인 위치 인덱스를 가져온다.
4. 그리고 다시 idx 인덱스를 사용하여 원본 데이터에서 해당 행을 추출한다.
5. month는 int타입으로 바꾼다.
 
결과

 
데이터시각화

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['font.family'] = 'AppleGothic'
plt.figure(figsize=(12, 6))

# 월별로 가장 인기 있는 영화의 인기도를 막대 그래프로 시각화
sns.barplot(data=monthly_top_movies, x='month', y='salesAmt', hue='movieNm')

plt.title('Monthly Top Movies by salesAmt',fontsize=20)
plt.xlabel('Month',fontsize=20)
plt.ylabel('salesAmt',fontsize=20)
plt.xticks(rotation=45,fontsize=20)
plt.legend(title='movieNm', bbox_to_anchor=(1.05, 1), loc='upper left', prop=fontprop,fontsize=20)
plt.tight_layout()

plt.show()

 
데이터시각화 결과
 
 

 
 
 

팀프로젝트를 진행하면서...

 

좋은점
 

팀원들과 함께 프로젝트를 진행하면서 다양한 에러를 마주치고 이를 해결해 나가는 과정에서 많은 것을 배울 수 있었다. 혼자 작업할 때는 이런 종류의 에러를 접할 기회가 상대적으로 적었는데, 팀프로젝트를 통해 서로의 지식과 경험을 공유하면서 문제를 해결하니 더 깊이 있게 배울 수 있었다. 에러를 하나씩 해결해 나가면서 성취감을 느낄 수 있었고, 팀원들과 협력하면서 문제 해결 능력도 업그레이드된 느낌?

 
아쉬운점
 

기능 구현에 너무 집중한 나머지 풀리퀘스트나 릴리즈 노트 작성에 소홀했다. 특히 릴리즈 노트와 같은 문서화 작업은 프로젝트의 관리와 협업에 매우 중요함에도 불구하고, 이에 대한 이해와 실천이 부족했다. 또한, README 파일을 어떤 방식으로 작성해야 할지 잘 감이 잡히지 않았다. README는 프로젝트를 처음 접하는 사람들에게 중요한 정보를 제공하는 문서인데, 이를 효과적으로 작성하지 못해 아쉬웠다. 판다스(Pandas)를 잘 몰라서 데이터 시각화 작업에도 어려움을 겪었는데, 이는 데이터를 효과적으로 분석하고 시각화하는 데 있어서 큰 걸림돌이 되었다.

 
개선할점
 

README 파일, 풀 리퀘스트, 릴리즈 노트 작성법을 배우기 위해 다른 사람들의 예시를 참고해야겠다. 깃허브에는 많은 좋은 예시들이 있는데, 이를 통해 문서화의 중요성을 이해하고 어떻게 작성해야 하는지 배우는 것이 필요할 것 같다. 예시들을 찾아보면서 나만의 스타일로 문서화를 해보는 것도 좋은 연습이 될 것같다. 또한, 판다스와 같은 데이터 분석 도구에 대해 더 깊이 공부하여 데이터 시각화 능력을 향상시켜야야겠다. 데이터 시각화는 단순히 데이터를 보기 좋게 만드는 것이 아니라, 데이터를 통해 인사이트를 도출하는 중요한 과정이므로 이를 제대로 활용할 수 있어야 좋겠지! 튜토리얼을 참고하거나, 작은 프로젝트를 통해 실습해야겠다.
 
프로젝트 2일차 칸반보드

 
프로젝트 3일차 칸반보드