종합 보고서의 내용 및 순서
개요
1) 배경 및 이유 정리
- 만든 사람과 보는 사람의 얼라인을 먼저 정확히 맞추기 위함
- 보고서를 만들기 전에 보고를 받아볼 사람에게 '난 이렇게 이해하고 이런 내용을 정리하려고 하는데, 원하는게 이게 맞아?' 하고 반드시 방향이 맞는지 확인 후 보고서를 만들 것.
2) 참고사항
- 보고서를 작성하며 있었던 특이사항, 변수, 이슈 등에 대해 어떻게 정의하고 시작했는지 등을 사전에 안내
- 화이트데이, 발렌타인데이 등 평소와 다른 요일에 주문수가 많이 나왔을 경우처럼, 특별한 예외사항에 대해 사전 안내 (제외하고 진행하였다 등)
- 데이터 집계 기간, 주요항목 (EX. 주문수 중에서도 어떤 조건을 충족한 주문수인지 명확히 정의)
- 상황을 모두 알고있는 우리팀원들이 아니라, 타팀원이나 상위자에게까지 보고가 필요한 내용이라면 그 외 공유사항을 더 상세히 작성
3) 결론
- 분석 내용 요약 및 의견 요약하기
- 보는 사람이 가장 궁금해하는 부분이기 때문에 상단에 먼저 요약정리해주는 것
- 위의 배경과 참고사항을 알고 결론을 봐야 더 정확한 의미 전달이 가능해짐
(이력서에서도... 성과와 회고를 먼저 적어야 하는 이유일까?)
현황 파악
1) 데이터 통계 자료
- 분석의 근거가 되는 자료, 결론의 힌트가 되는 자료
- 어떤 부분에 문제가 있는지, 어떤 부분이 눈에 띄는 차이가 있는지 한 눈에 확인할 수 있도록.
- 사실상 가장 시간이 오래 걸리는 부분이지만, 보고서에는 그 중에서도 의미있는 자료(핵심 근거)만 싣는다.
분석
1) 가설 및 분석
- 어떻게 가설을 세웠는지, 그걸 증명하기 위해 어떤 데이터를 보았는지
- 그냥 자료(표, 차트 등)만 던질 경우 보는 사람이 다시 한 번 그 자료를 분석하기 위해 시간과 에너지를 써야 함. 자료를 붙일 때는 반드시 나의 해석을 함께 공유할 것
보고서 작성 시 주의해야 할 점
1. 목적을 달성할 수 있는 결론을 도출
- 성과분석이 목적이면, 성공했는지 여부를 작성
- 이슈파악이 목적이면, 원인이 무엇이었는지, 어떻게 개선할 수 있을지 작성
2. 애매한 표현보다는 객관적으로 전달
- ~인 것 같습니다 (X) ~인 것으로 확인하였습니다 (O)
3. 데이터 기반으로 가설을 증명
- 뽑은 데이터를 시각화해 자료로 활용
4. 보고서의 흐름과 생각의 흐름 일치
- A > B > C 의 순서에서 중간을 생략하지 말 것. 처음 보는 사람도 매끄럽게 전 과정을 이해할 수 있도록
5. 쉬운 단어 / 표현 사용
- 한 용어에 대해서도 다른 팀에서는 다른 용어를 사용할 수 있음. (배달비/배달팁...) 어떤 의미의 용어인지 명확히 할 것
- 용어 대신 문장 형태로 풀어서 써주는 것도 좋음 (길어지더라도 이해가 안되는 단어가 등장하는 것보다 나음)
보고서 예시
- 더 좋은 작성방법이 얼마든지 있을 수 있음. 나만의 보고서 작성법을 앞으로 디벨롭해나갈 것
위에서 이용된 표를 뽑는 쿼리
1. 월별 매출 비교하기
with sales_raw as
(SELECT
substr(cast(a.reserv_date as string),1,6) as part_month,
c.item_id,
c.product_name,
count(a.reserv_no) as cnt,
sum(b.sales) as sales
-- sum(sum(b.sales)) over (partition by substr(cast(a.reserv_date as string),1,6)) as dense_sum,
-- round( sum(b.sales) /sum(sum(b.sales)) over (partition by substr(cast(a.reserv_date as string),1,6)) *100,0) as portion
FROM `ls-data-literacy-301513.practice.reservation`a
left join `ls-data-literacy-301513.practice.order_info` b on a.reserv_no = b.reserv_no
left join `ls-data-literacy-301513.practice.item` c on c.item_id = b.item_id
where cast(cancel as string) = 'false'
group by 1,2,3
)
select
part_month,
item_id,
product_name,
cnt,
sales,
sum(sales) over (partition by part_month) as dense_sum,
round(sales/ sum(sales) over (partition by part_month)*100,0) as yearly_portion
from sales_raw
group by 1,2,3,4,5
<출력값>
part_month | item_id | product_name | cnt | sales | dense_sum | yearly_portion |
201706 | M0002 | PASTA | 1 | 24000 | 613000 | 4 |
201706 | M0007 | SALAD | 1 | 30000 | 613000 | 5 |
201706 | M0009 | WINE | 1 | 8000 | 613000 | 1 |
201706 | M0001 | SPECIAL_SET | 3 | 144000 | 613000 | 23 |
201706 | M0008 | SANDWICH | 1 | 10000 | 613000 | 2 |
201706 | M0005 | STEAK | 3 | 385000 | 613000 | 63 |
201706 | M0010 | JUICE | 1 | 12000 | 613000 | 2 |
201707 | M0001 | SPECIAL_SET | 6 | 408000 | 1744000 | 23 |
201707 | M0010 | JUICE | 3 | 36000 | 1744000 | 2 |
201707 | M0002 | PASTA | 6 | 228000 | 1744000 | 13 |
201707 | M0008 | SANDWICH | 4 | 70000 | 1744000 | 4 |
201707 | M0003 | PIZZA | 4 | 102000 | 1744000 | 6 |
201707 | M0005 | STEAK | 5 | 525000 | 1744000 | 30 |
201707 | M0004 | SEA_FOOD | 3 | 175000 | 1744000 | 10 |
201707 | M0006 | SALAD_BAR | 4 | 200000 | 1744000 | 11 |
201708 | M0001 | SPECIAL_SET | 6 | 336000 | 1622000 | 21 |
201708 | M0010 | JUICE | 2 | 42000 | 1622000 | 3 |
201708 | M0005 | STEAK | 5 | 455000 | 1622000 | 28 |
201708 | M0008 | SANDWICH | 5 | 90000 | 1622000 | 6 |
201708 | M0006 | SALAD_BAR | 3 | 175000 | 1622000 | 11 |
201708 | M0004 | SEA_FOOD | 4 | 225000 | 1622000 | 14 |
201708 | M0002 | PASTA | 5 | 108000 | 1622000 | 7 |
201708 | M0003 | PIZZA | 5 | 136000 | 1622000 | 8 |
201708 | M0009 | WINE | 3 | 40000 | 1622000 | 2 |
201708 | M0007 | SALAD | 1 | 15000 | 1622000 | 1 |
201709 | M0005 | STEAK | 4 | 280000 | 1586000 | 18 |
201709 | M0004 | SEA_FOOD | 4 | 300000 | 1586000 | 19 |
201709 | M0003 | PIZZA | 8 | 238000 | 1586000 | 15 |
201709 | M0002 | PASTA | 5 | 120000 | 1586000 | 8 |
201709 | M0001 | SPECIAL_SET | 4 | 264000 | 1586000 | 17 |
201709 | M0008 | SANDWICH | 2 | 30000 | 1586000 | 2 |
201709 | M0010 | JUICE | 2 | 48000 | 1586000 | 3 |
201709 | M0009 | WINE | 1 | 16000 | 1586000 | 1 |
201709 | M0007 | SALAD | 2 | 90000 | 1586000 | 6 |
201709 | M0006 | SALAD_BAR | 4 | 200000 | 1586000 | 13 |
201710 | M0006 | SALAD_BAR | 6 | 425000 | 3333000 | 13 |
201710 | M0005 | STEAK | 10 | 1120000 | 3333000 | 34 |
201710 | M0004 | SEA_FOOD | 3 | 200000 | 3333000 | 6 |
201710 | M0002 | PASTA | 9 | 336000 | 3333000 | 10 |
201710 | M0007 | SALAD | 3 | 135000 | 3333000 | 4 |
201710 | M0003 | PIZZA | 8 | 391000 | 3333000 | 12 |
201710 | M0001 | SPECIAL_SET | 6 | 552000 | 3333000 | 17 |
201710 | M0010 | JUICE | 4 | 102000 | 3333000 | 3 |
201710 | M0009 | WINE | 1 | 32000 | 3333000 | 1 |
201710 | M0008 | SANDWICH | 3 | 40000 | 3333000 | 1 |
201711 | M0006 | SALAD_BAR | 5 | 400000 | 5197000 | 8 |
201711 | M0008 | SANDWICH | 7 | 150000 | 5197000 | 3 |
201711 | M0009 | WINE | 6 | 304000 | 5197000 | 6 |
201711 | M0010 | JUICE | 5 | 174000 | 5197000 | 3 |
201711 | M0005 | STEAK | 12 | 1715000 | 5197000 | 33 |
201711 | M0004 | SEA_FOOD | 5 | 425000 | 5197000 | 8 |
201711 | M0001 | SPECIAL_SET | 13 | 888000 | 5197000 | 17 |
201711 | M0007 | SALAD | 4 | 105000 | 5197000 | 2 |
201711 | M0002 | PASTA | 10 | 492000 | 5197000 | 9 |
201711 | M0003 | PIZZA | 15 | 544000 | 5197000 | 10 |
201712 | M0005 | STEAK | 42 | 4900000 | 10862000 | 45 |
201712 | M0009 | WINE | 13 | 456000 | 10862000 | 4 |
201712 | M0001 | SPECIAL_SET | 21 | 3216000 | 10862000 | 30 |
201712 | M0008 | SANDWICH | 13 | 220000 | 10862000 | 2 |
201712 | M0006 | SALAD_BAR | 9 | 675000 | 10862000 | 6 |
201712 | M0003 | PIZZA | 8 | 255000 | 10862000 | 2 |
201712 | M0004 | SEA_FOOD | 5 | 300000 | 10862000 | 3 |
201712 | M0007 | SALAD | 3 | 150000 | 10862000 | 1 |
201712 | M0002 | PASTA | 19 | 660000 | 10862000 | 6 |
201712 | M0010 | JUICE | 2 | 30000 | 10862000 | 0 |
2. 총 상품의 주문취소율 대비 스페셜 세트의 주문취소율
with sales_raw as
(SELECT
substr(cast(a.reserv_date as string),1,6) as part_month,
case when c.item_id = 'M0001' then 'specail' else 'etc' end as item_type,
c.product_name,
count(a.reserv_no) as cnt,
count(case when cast(cancel as string) = 'true' then a.reserv_no end) as canceled_reserv_cnt,
count(case when cast(cancel as string) = 'false' then a.reserv_no end) as completed_reserv_cnt,
sum(b.sales) as sales
FROM `ls-data-literacy-301513.practice.reservation`a
left join `ls-data-literacy-301513.practice.order_info` b on a.reserv_no = b.reserv_no
left join `ls-data-literacy-301513.practice.item` c on c.item_id = b.item_id
--where cast(cancel as string) = 'false'
group by 1,2,3
)
select
part_month,
sum(cnt) as cnt,
sum(sales) as total_sales,
sum(case when item_type = 'specail' then sales end) as special_sales,
round(sum(case when item_type = 'specail' then sales end) / sum(sales) *100,0) as special_portion,
sum(case when item_type = 'specail' then canceled_reserv_cnt end) as special_canceled_reserv_cnt,
round(sum(case when item_type = 'specail' then canceled_reserv_cnt end) /sum(cnt)*100,1) as specail_cancel_rate,
sum(canceled_reserv_cnt) as canceled_cnt,
sum(completed_reserv_cnt) as completed_cnt,
round(sum(canceled_reserv_cnt)/sum(cnt)*100,1) as cancel_rate
from sales_raw
group by 1
order by 1
part_month | cnt | total_sales | special_sales | special_portion | special_canceled_reserv_cnt | specail_cancel_rate | canceled_cnt | completed_cnt | cancel_rate |
201706 | 13 | 613000 | 144000 | 23 | 0 | 0 | 2 | 11 | 15.4 |
201707 | 41 | 1744000 | 408000 | 23 | 0 | 0 | 6 | 35 | 14.6 |
201708 | 45 | 1622000 | 336000 | 21 | 0 | 0 | 6 | 39 | 13.3 |
201709 | 41 | 1586000 | 264000 | 17 | 0 | 0 | 5 | 36 | 12.2 |
201710 | 61 | 3333000 | 552000 | 17 | 0 | 0 | 8 | 53 | 13.1 |
201711 | 94 | 5197000 | 888000 | 17 | 0 | 0 | 12 | 82 | 12.8 |
201712 | 155 | 10862000 | 3216000 | 30 | 0 | 0 | 20 | 135 | 12.9 |
지난 번 wau 감소 분석 프로젝트에서도 느꼈지만...
실제로 실무에서 활용하게 될 sql은 문제풀이에서 보는 것보다
1) 훨씬 길고
2) 그렇지만 단순한 함수의 조합으로 (count, sum, avg, 곱셈 나눗셈 비율표현 등)
3) 각 지표를 정확한 데이터를 이용해 정의하고 (Z라는 목적을 위해, A를 B로 나누어 C의 의미를 가진 D 지표를 뽑겠다)
4) 오류가 없도록 정확하게 짜야 한다.
그렇게 뽑은 sql은 이후로 진행할 데이터 분석의 재료가 될 뿐이고.
지금은 이렇게 보고서 하나 쓰는 것도 시간이 얼마나 걸릴지 계산하게 되고 까마득하고 아득해보이지만...
실무에서 활용하기 시작하면 6개월에서 1년이면 충분히 능숙하고 자신있게 쓸 수 있게 되지 않을까 싶다.
이렇게 번갯불에 콩 볶듯 완강한 강의!
sql이나 구글시트 활용 부분은 예상보다도 훨씬 더 쉬워 아쉬웠지만, 마지막의 보고서 작성 챕터는 좋았다.
배민의 사업개발 팀에서 일하시는 분도 이 정도의 분석 스킬, 보고서 작성 능력으로 비즈니스 업무를 진행하는 데에 크게 무리가 없구나. 를 알게 되어 조금은 자신감이 생겼다.
이 정도는 기본으로 치고, 이외에 더 엣지를 더하고 싶다면
1) 추가로 더 고도의 데이터 분석 스킬을 익히거나
2) 나만의 강점이 될 수 있는 다른 역량을 더 갈고닦아야 할 것이다.
화이팅!
'[공부] 데이터 분석 활용' 카테고리의 다른 글
[SQL 강의] 러닝스푼즈 | SQL과 구글 시트로 배우는 데이터 리터러시 첫 걸음 (4) 데이터 분석 방법론 (1) | 2024.02.18 |
---|---|
[SQL 강의] 러닝스푼즈 | SQL과 구글 시트로 배우는 데이터 리터러시 첫 걸음 (3) 스프레드시트 활용 (1) | 2024.02.04 |
[SQL 강의] 러닝스푼즈 | SQL과 구글 시트로 배우는 데이터 리터러시 첫 걸음 (2) SQL 활용 (0) | 2024.02.01 |
[SQL 강의] 러닝스푼즈 | SQL과 구글 시트로 배우는 데이터 리터러시 첫 걸음 (1) (1) | 2024.01.30 |
[SQL 실전 무료강의] 프로젝트 1. 주간 활성 유저 감소 (0) | 2024.01.19 |