데싸 이모저모/시각화 스터디

시각화 | Kaggle - Coffee Shop Sales (2)

shchannel 2025. 5. 23. 10:00

오늘은 이전 글에 이어서, 뉴욕에 위치한 세 곳의 커피숍 거래 데이터를 바탕으로

시각화와 함께 판매 트렌드를 어떻게 분석했는지 정리해 보려고 한다.

 

☕️ 오늘의 스터디

💡 Topic: 뉴욕에 위치한 세 곳의 커피숍 거래 내역

🤓 Goal: 지점의 위치별 시간대별 상품 판매율 등

📚 Data: Coffee Shop Sales

자유롭게 세부 목표와 계획을 가지고 데이터 분석과 시각화를 진행하고 과정과 결과를 공유해 보자!

 

👩‍💻 분석 및 시각화

시각화는 비교적 익숙한 seaborn & matplotlib과 plotly를 사용하여 주제별로 2가지의 그래프 방식을 비교해 보았다.

 

1. 월별 데이터 분석

1-1. 월별 주문건수

먼저 월별로 주문건수를 시각화하기 위하여 변수를 생성한 후,

Searborn의 countplot과 Plotly의 bar chart를 이용하여 시각화하였다.

# 월별 주문건수 카운트
transactions_per_month = df['month'].value_counts()
## 1) seaborn - countplot
ax = sns.countplot(x = 'month', data=df, palette='Set3')  # 팔레트를 통해 범주별 색상 다르게 적용
plt.title('Transactions per Month')

# 막대 위에 라벨 붙이기
for container in ax.containers:
    ax.bar_label(container)

plt.show()

## 2) plotly - bar
# nbformat 설치 필요
fig = px.bar(x=transactions_per_month.index, 
            y=transactions_per_month.values,  
            labels = {'x' : 'Month', 'y' : 'Number of Transactions'},
            title = 'Transcation per Month', text_auto=True)

fig.show()

1) searborn - countplot / / / 2) plotly - bar

📍결과

  • 6월35,352건으로 가장 많고
  • 2월16,359건으로 가장 적었다.

 

우측에 Plotly 그래프를 확인하면 5월(May)의 우측 상단에 대화 상자가 생긴 것을 볼 수 있는데,

이 대화상자는 마우스오버 시에 볼 수 있는 데이터 상세정보이다.

Plotly는 이처럼 그래프를 고정적인 이미지로만 보는 것이 아니라 특정 데이터의 값 확인, 줌인/줌아웃 등 탐색적 데이터 분석에 필요한 요소를 더욱 쉽게 볼 수 있는 장점이 있다. 

 

1-2. 월별 주문금액

이번에는 월별로 단순 건수 카운트가 아니라 총 주문금액을 계산하여 시각화했다.

## 월별 주문금액 계산
total_revenue_per_month = df.groupby('month')['total_revenue'].sum().sort_values(ascending=False).reset_index()

## 1) seaborn - bar
ax = sns.barplot(x = 'month', y = 'total_revenue', data=total_revenue_per_month, palette='Set3')  # 팔레트를 통해 범주별 색상 다르게 적용
plt.title('Total Revenue per Month')

for container in ax.containers:
    ax.bar_label(container)

plt.show()

## 2) plotly - bar
fig = px.bar(x=total_revenue_per_month['month'], 
            y=total_revenue_per_month['total_revenue'],  
            labels = {'x' : 'Month', 'y' : 'Total Revenue'},
            title = 'Total Revenue per Month', text_auto=True)

fig.show()

1) seaborn - bar / / / 2) plotly - bar

📍 결과는 주문건수와 유사하게,

  • 6월이 가장 높은 매출
  • 2월이 가장 낮은 매출로 나타났다.

2. 요일별 데이터 분석

이번에는 월별 데이터 분석과 유사하게 요일별로 주문건수와 주문금액을 시각화하였다.

Seaborn의 그래프도 내림차순으로 정렬하고, Plotly 그래프에도 컬러를 넣어주어 조금 더 범주의 구분이 돋보이게 하였다.

2-1. 요일별 주문건수

## 요일별 주문건수 카운트
transactions_per_day = df['day'].value_counts().sort_values(ascending=False)

## 1) seaborn - countplot
ax = sns.countplot(data=df, x='day', hue='day', palette='Set2', order=transactions_per_day.index)  # hue: 색상 그룹핑 기준을 지정해주는 것을 권장! 안하면 warning 발생
plt.title('Transactions per Day')

for container in ax.containers:
    ax.bar_label(container)

plt.show()

## 2) plotly - bar
fig = px.bar(x = transactions_per_day.index,
             y = transactions_per_day.values,
             labels = {'x':'Day', 'y':'Number of Transactions'},
             title = 'Transcations per Day',
             color = transactions_per_day.index,  # 요일마다 색 다르게
             color_discrete_sequence = px.colors.qualitative.Set2,  # 색상 팔레트를 리스트형태로 불러와야 함. sns처럼 문자열로 작성하면 에러 발생
             text_auto = True)

fig.show()

1) seaborn - countplot / / / 2) plotly - bar

 

2-2. 요일별 주문금액

## 요일별 주문금액 계산
total_revenue_per_day = df.groupby('day')['total_revenue'].sum().sort_values(ascending=False).reset_index()

## 1) seaborn - barplot
ax = sns.barplot(data = total_revenue_per_day,
            x = 'day',
            y = 'total_revenue',
            hue = 'day',
            palette = 'pastel')
plt.title('Total Revenue per Day')

for container in ax.containers:
    ax.bar_label(container)

plt.show()

## 2) plotly - bar
fig = px.bar(x = total_revenue_per_day['day'],
             y = total_revenue_per_day['total_revenue'],
             labels = {'x':'Day', 'y':'Total Revenue'},
             title = 'Total Revenu per Day',
             text_auto = True,
             color = total_revenue_per_day['day'],
             color_discrete_sequence = px.colors.qualitative.Pastel1)

fig.show()

1) searborn - bar / / / 2) plotly - bar

📍 결과

  • 요일 간 주문건수와 주문금액에 큰 차이는 없다.
  • 금요일: 가장 많은 주문건수 (21,701건)
  • 월요일: 가장 높은 주문금액 (101,677달러)
  • 주말(토/일)상대적으로 낮은 주문량과 매출

3. 시간대별 데이터 분석

어느 시간대에 주문이 몰리는지를 파악하기 위하여 시간대별 거래 데이터를 확인해 보았다.

3-1. 시간대별 주문건수

## 시간대별 주문건수 카운트
df['hour'] = df['transaction_time'].apply(lambda x: x.hour)  # transaction_time 컬럼의 각 값에 대해 .hour 속성 추출

## seaborn - kdeplot
sns.kdeplot(df['hour'])
plt.title('Most Sales per each hour')
plt.show()

 

이번에는 Seaborn의 kdeplot을 사용하여 시간대별 주문 분포를 시각화했다.

kdeplot은 확률밀도함수를 기반한 그래프로,

히스토그램과 유사하지만 명시적인 개수보다는

데이터의 분포를 더 부드러운 곡선 형태로 표현하여 트렌드를 파악하는데 유용하다.

 

📍 결과

  • 오전 7시 ~ 11시 사이가 피크타임
  • 오후 시간은 상대적으로 한산하다.

 

 

3-2. 시간대별 상품별 주문건수

그럼 각 시간대별로 어떤 상품이 가장 많이 팔리는지도 함께 확인했다.

## 시간대별 상품별 주문건수 카운트
sales_per_hour = df.groupby(['hour', 'product_category'])['transaction_qty'].sum().reset_index()

## 1) seaborn - line plot
ax = sns.lineplot(data = sales_per_hour,
             x = 'hour',
             y = 'transaction_qty',
             hue = 'product_category',
             marker = 'o',
             palette = 'dark')

plt.title('Transaction Quantity per Hour by Product Category')
plt.xlabel('Hour')
plt.ylabel('Total Quantity')
plt.xticks(range(0, 24))
plt.tight_layout()

plt.show()

## 2) plotly - bar_stack
fig = px.bar(sales_per_hour, 
             x = 'hour',
             y = 'transaction_qty',
             color = 'product_category',
             title = 'Transaction Quantity per Hour by Product Category',
             labels = {'transaction_qty':'Total Quantity'},
             barmode = 'stack',   # 또는 group으로 변경 가능
             color_discrete_sequence = px.colors.qualitative.Set2)

fig.show()

1) seaborn - line plot / / / 2) plotly - bar_stack mode

📍 결과

  • 전 시간대에 걸쳐 Coffee압도적인 비중으로 판매된다.
  • 그 뒤를 Tea가 따른다.
  • Packaged Chocolate거의 판매되지 않는 것으로 보인다.

이처럼 잘 보이지 않는 상세 데이터 확인을 위하여 Plotly 그래프에서는 마우스 드래그를 활용한 줌인 기능도 제공한다.

위의 plotly - bar_stack mode 그래프를 확대한 모습은 다음과 같다. 

전체 그래프에서는 확인하기 어려웠던 Coffee beans의 오전 10시 주문건수는 345건임을 상세히 확인할 수 있었다.

 

4. 지점별 분석

Maven Roasters의 지점은 Lower Manhattan, Hell's kitchen, Astoria 이렇게 3곳이 있다.

그렇다면 이번에는 각 지점별 판매 데이터에 대하여 들여다보았다.

4-1. 지점별 수익

어느 지점이 수익이 가장 많을까? 지점별로 수익에 차이가 있을까?

## plotly - pie chart
fig = px.pie(df,                          # 원형 차트 pie
             names = 'store_location',    # 각 파이 조각의 레이블 대상
             values = 'total_revenue',    # 파이 조각의 크기 설정_매장 위치별 총 수익 값을 기준으로 크기 결정
             title = 'Total Revenue Distribution by Store Location',
             hole = 0.3)                  # 차트 중앙에 구멍 영역 설정

fig.show()

📍 결과

 

  • Hell's Kitchen이 약 236,511달러(33.8%)로 가장 많은 수익
  • Astoria: 약 232,244달러(33.2%)
  • Lower Manhattan: 약 230,057달러(32.9%)
    → 수익 차이는 모두 약 33%로 비슷한 수준이나, Hell's Kitchen이 소폭 앞선다.

4-2. 지점별 상품 카테고리 판매량

그렇다면 지점별로 가장 잘 판매되는 상품에는 차이가 있을까?

(모두 커피가 가장 많을 것으로 예상되었으나, 그래도 직접 확인해 보기로 했다.)

## 지점별 상품 판매량 계산
sales_per_location = df.groupby(['store_location', 'product_category'])['transaction_qty'].sum().reset_index()

# 판매량을 테이블 형식으로 변환하는 피벗
pivot_sales = sales_per_location.pivot(index='store_location', columns = 'product_category', values = 'transaction_qty').fillna(0)

## 1) matplotlib - bar_stacked
pivot_sales.plot(kind='bar', figsize=(12,8), stacked=True, colormap='Set3')
plt.title('Transaction Quantity per Store Location by Product Category')
plt.xlabel("Store Location")
plt.ylabel("Total Quantity")
plt.tight_layout()
plt.show()

위에서 plotly로 구현했던 stacked bar plot을 이번에는 matplotlib을 기반으로 만들어 보았다.

눈으로 확인했을 때, 지점별로 상품별 판매량은 비슷할 것으로 생각된다.

 

그렇다면 이번에는 동일한 내용을 Seaborn의 히트맵과 Plotly bar chart를 통하여 상세 수량을 볼 수 있도록 구현했다.

## 2) seaborn - heatmap
plt.figure(figsize=(12,10))
sns.heatmap(pivot_sales, annot=True, cmap='YlGnBu', fmt='g', linewidths=0.5)
# annot=True: 각 셀에 실제 숫자 값 표시 / fmt='g': 숫자 표시 형식_일반 숫자
# 색상: 노랑 -> 초록 -> 파랑; 값이 높을수록 파란색으로 변해감

plt.title("Transaction Quantity per Store Location by Product Category")
plt.xlabel("Product Category")
plt.ylabel("Store Location")
plt.tight_layout()
plt.show()

## 3) plotly - bar_stack
fig = px.bar(sales_per_location, 
             x = 'store_location',
             y = 'transaction_qty',
             color = 'product_category', 
             title = 'Transaction Quantity per Store Location by Product Category', 
             labels = {'transaction_qty':'Total Quantity'},
             barmode = 'stack',
             color_discrete_sequence=px.colors.qualitative.Set3)

fig.show()

2) seaborn - heatmap / / / 3) plotly - bar_stack

좌측의 히트맵에서는 노랑 → 초록 → 파랑색으로 색상을 설정하여 값이 높을수록 진한 파란색으로 나타난다.

📍 결과

  • 모든 지점에서 Coffee > Tea > Bakery
  • 히트맵에서 Astoria의 Bakery 수치는 7,496
    → Plotly bar chart에서도 동일한 Total Quantity 수치를 확인할 수 있다.

히트맵은 색상 대비를 통해 한 눈에 수치 분포를 보기 좋았고,

Plotly는 세부 stacked bar를 통해 전체적인 분포와 세부 수치까지 같이 확인할 수 있는 점이 유용했다.

 

💡 정리

도구 설명
Seaborn 기본 통계 시각화에 유용. countplot, kdeplot, heatmap 등 사용
Matplotlib seaborn과 함께 사용하며, 커스터마이징 용이
Plotly 인터랙티브 기능 제공. 줌인, 마우스오버, 상세정보 확인에 최적화

 

✎ 마무리

이번 분석은 단순히 정답을 찾는 과정보다는,
데이터를 자유롭게 바라보며 원하는 시각화를 구현하는 것을 해볼 수 있는 과정이었다.

각 시각화 도구의 특성을 직접 만들고 체험해보며, 데이터를 탐색적으로 이해하기 위해 시각화도 얼마나 중요한지를 알아가고 있다.

아직은 다양한 그래프의 형태나, 어떤 데이터를 어떤 그래프로 표현해야 적절한지에 대한 감각이 부족하다 보니
그래프를 선택하고 구현하는 데에 어려움도 있었다.

그래서 다음에는 데이터의 유형에 따라 어떤 시각화가 적절한지 정리해보는 시간을 가져보면 좋을 것 같다.🤓