240626_TIL

J Lee·2024년 6월 26일
0

아무리 사소하더라도 배움이 없는 날은 없다.

SQL 코드카타

오늘은 7문제 풀이 완료.
이제 10문제 남았으니 목요일, 금요일에 각각 5문제씩 풀면 끝날 듯.
프로그래머스의 SQL문제 난이도는 전체적으로 leetcode에 비해 낮은 것 같다.

leetcode 유료결제를 해야 하나 진짜..?🤔

문제 링크

SELECT e1.id,
       Ifnull(Count(e2.id), 0) AS CHILD_COUNT
FROM   ecoli_data e1
       LEFT JOIN ecoli_data e2
              ON e1.id = e2.parent_id
GROUP  BY 1
ORDER  BY 1 

문제 링크

WITH score
     AS (SELECT emp_no,
                Avg(score) AS avg_score
         FROM   hr_grade
         GROUP  BY 1)
SELECT he.emp_no,
       he.emp_name,
       CASE
         WHEN avg_score >= 96 THEN 'S'
         WHEN avg_score >= 90 THEN 'A'
         WHEN avg_score >= 80 THEN 'B'
         ELSE 'C'
       END AS GRADE,
       CASE
         WHEN avg_score >= 96 THEN he.sal * .2
         WHEN avg_score >= 90 THEN he.sal * .15
         WHEN avg_score >= 80 THEN he.sal * .1
         ELSE he.sal * 0
       END AS BONUS
FROM   hr_employees he
       INNER JOIN score
               ON he.emp_no = score.emp_no
ORDER  BY 1;

문제 링크

SELECT CASE
         WHEN Month(differentiation_date) BETWEEN 1 AND 3 THEN '1Q'
         WHEN Month(differentiation_date) BETWEEN 4 AND 6 THEN '2Q'
         WHEN Month(differentiation_date) BETWEEN 7 AND 9 THEN '3Q'
         WHEN Month(differentiation_date) BETWEEN 10 AND 12 THEN '4Q'
       end       AS QUARTER,
       Count(id) AS ECOLI_COUNT
FROM   ecoli_data
GROUP  BY 1
ORDER  BY 1;

문제 링크

WITH a
     AS (SELECT it1.item_id
         FROM   item_tree it1
                LEFT JOIN item_tree it2
                       ON it1.item_id = it2.parent_item_id
         WHERE  it2.item_id IS NULL)
SELECT a.item_id,
       ii.item_name,
       ii.rarity
FROM   a
       INNER JOIN item_info ii
               ON a.item_id = ii.item_id
ORDER  BY 1 DESC; 

문제 링크

WITH a
     AS (SELECT fish_type,
                Avg(Ifnull(length, 10)) AS avg_length
         FROM   fish_info
         GROUP  BY 1
         HAVING avg_length >= 33)
SELECT Count(*)    FISH_COUNT,
       Max(length) MAX_LENGTH,
       a.fish_type
FROM   fish_info fi
       INNER JOIN a
               ON fi.fish_type = a.fish_type
GROUP  BY 3
ORDER  BY 3;

문제 링크

SELECT route,
       Concat(Round(Sum(d_between_dist), 1), 'km') AS TOTAL_DISTANCE,
       Concat(Round(Avg(d_between_dist), 2), 'km') AS AVERAGE_DISTANCE
FROM   subway_distance
GROUP  BY 1
ORDER  BY 1 DESC 

문제 링크

WITH a
     AS (SELECT fish_type,
                Max(length) AS length
         FROM   fish_info
         GROUP  BY 1),
     b
     AS (SELECT a.fish_type,
                a.length,
                fni.fish_name
         FROM   a
                INNER JOIN fish_name_info fni
                        ON a.fish_type = fni.fish_type),
     c
     AS (SELECT fi.id,
                b.fish_name,
                b.length
         FROM   b
                INNER JOIN fish_info fi
                        ON b.fish_type = fi.fish_type
                           AND b.length = fi.length)
SELECT *
FROM   c
ORDER  BY 1 

클러스터링 프로젝트

🌺 데이터셋은 총 5개의 CSV 파일로 구성되어 있습니다.

  • Retail_dataset.csv 파일에 ERD(테이블 구조도) 및 컬럼 설명이 기재되어 있습니다.

🌺 각 테이블을 결합하여, 클러스터링을 위한 하나의 데이터셋으로 만들어주세요.

  • 모든 테이블을 결합하지 않아도 좋습니다.
  • 다만, 테이블 결합 시 알맞은 결합 방식을 사용해주세요.

🌺 필수 사항

  • 이상치 처리 기법을 활용하거나, 특정 기준을 세워 이상치를 정의 하고 그 이유를 설명해주세요.
  • 클러스터링시, 초기 군집의 갯수와 사용할 컬럼의 갯수는 python 머신러닝 라이브러리를 활용하여 진행해주세요.
  • 컬럼별 raw data 분포를 그려주세요.
  • 컬럼 간 상관계수를 히트맵 차트로 구현해주세요.(유의미한 기준은 +0.6(양의 상관관계) 또는 -0.6(음의 상관관계)으로 판단해주시면 됩니다)

🌺 선택 사항

  • 필요하다면 파생변수를 생성해도 좋습니다.
  • 데이터 표준화가 필요한 경우 진행해주세요. 표준화 방법을 여러가지 사용해보시고, 비교해주셔도 됩니다.
  • 범주형 데이터를 사용할 경우, 인코딩을 진행해주세요. 원-핫 인코딩/라벨인코딩 모두 사용해도 됩니다. 다만, 범주의 갯수가 많은 경우, 별도 세그멘테이션이 필요할 수 있겠습니다. 의미있는 기준을 세워주시고 그 값을 인코딩 진행해주세요. (예시: 국가가 100개인 경우 육대륙으로 나누어 인코딩). 참고자료: https://nicola-ml.tistory.com/62#google_vignette
  • 분석 결과를 한 눈에 파악할 수 있도록 datapane 으로 리포트를 구현해주세요.

2. EDA

오늘은 orders 테이블과 payments 테이블을 합쳐주는 단계까지 진행했다.

먼저 orders와 payments를 합치기 전에 payments 테이블의 구조를 살펴보면

이렇게 되어 있는데,
하나의 order_id 밑에 여러 개의 다른 컬럼이 붙어있는 경우가 있었다.

따라서, 이런 중복 행들을 사전에 처리하지 않고 orders 테이블과 payment 테이블을 합칠 경우 (조건을 inner로 한다면) 여러 개의 값들 중 하나만 orders 테이블에 가서 붙거나, (조건을 left로 한다면) 붙여놓은 결과 테이블에서 중복 행이 발생하는 문제가 생길 것 같다. 따라서 orders 테이블과 합치기 전에 어떤 컬럼을 어떻게 사용할지부터 결정하기로 했다.

  • payment_sequential은 하나의 주문을 몇 번에 걸쳐서 결제하는지를 나타내는 컬럼인데, 하나의 주문에 여러 개의 sequential이 발생한다고 해도 최종적으로 orders 테이블에 꽂히는 주문은 1건이다. 따라서 군집 분류에 효과적으로 쓰일지는 모르지만, 각 order_id별로 최대값 하나만 살리기로 했다. (아래 이미지의 예에서는 19)

  • payment_type는 결제수단을 나타내는 컬럼인데, 총 5개의 고유한 값이 있다. 이 중 credit_card와 voucher는 봤는데 wallet, debit_card, not_defined의 정체를 좀 더 알아보기로 했다.

    우선 가장 간단한 not_defined를 보면, 이건 그냥 주문 취소 건이다. not_defined 값을 가진 order_id는 3개밖에 없고, 이걸 orders 테이블에서 조회해 보면 주문 상태에 canceled라고 뜬다. 따라서 이건 그냥 없애도 될 것 같다.

    wallet과 debit_card는 각각 전자지갑(애플페이나 페이팔 등)과 직불카드(우리가 흔히 얘기하는 체크카드)를 의미하는데, 전체적인 결제수단별 분포를 살펴보면 credit_card가 75% 정도 되고, 나머지 결제수단을 합친 것들이 25% 정도 되는 것 같다.

    payment_type에서는 not_defined 값인 것들은 삭제하고 나머지들은 order_id별로 사용한 결제수단의 갯수를 세어주기로 했다. 일단 그루핑해 보고 분포가 어떻게 나오는지를 봐야 감이 올 듯.

  • payment_installments는 신용카드(credit_card)의 할부 수를 나타낸 컬럼인데, credit card 외에 wallet이나 voucher등은 무조건 1로 들어가 있다. credit_card도 1로 들어가 있는 경우가 있는데 이건 일시불인 것 같다. 따라서 order_id 별로 이 컬럼을 묶어야 한다면 최대값을 쓰는 것이 가장 자연스러울 것 같다. 만약 최대값을 썼을 경우

    1. 1로 나온다 → credit_card든 다른 결제수단을 섞어서 쓰든 하여튼 일시불
    2. 1이 아닌 다른 값(2 이상)이 나온다 → credit card의 할부 수 (다른 결제수단은 2 이상일 수 없으므로)

    어제는 payment_installments의 값이 고정되어 있는 하나의 값이라고 생각했는데 데이터를 자세히 보면서 그렇지만은 않을 수도 있다는 걸 알게 됨.

  • 마지막으로 payment_value는 결제액이므로 order_id별로 합산(sum)해서 쓰면 될 것 같다.

정리하자면, payment 테이블에서는

  1. payment_type에서 not_defined 값 제거
  2. order_id별로 그루핑하되
    1. payment_sequential 컬럼은 최대값 사용
    2. payment_type 컬럼은 고유값의 수 사용
    3. payment_installment 컬럼은 최대값 사용
    4. payment_value 컬럼은 합계 사용

이 될 것 같다. 이 조건대로 데이터를 변환해 보면

원래 10만 행이 조금 넘던 payments 테이블이 99,437 행으로 줄어든 것을 확인할 수 있다. 그루핑이 원하는 대로 잘 수행됐는지를 체크하기 위해 중복되는 order_id가 있는지 여부를 확인해 봤다.

중복된 order_id 없이 잘 그루핑된 것 같다.

이제 이 결과(payment2_groupby)를 order_id와 하나로 합쳐보기로 했다.
기준이 되는 테이블은 orders이고 조건은 order_id를 기준으로 left로 수행하기로 했다. inner가 아니라 left로 수행하는 이유는 혹시나 join 조건이 안 맞아서 행 수가 줄어들었을 때 어떤 컬럼에서 문제가 생긴 건지 체크가 어렵기 때문.

합쳐진 결과 orders 테이블의 오른쪽에 payment_sequential부터 payment_value까지 4개의 컬럼이 추가되었다. 조건이 left였으므로 혹시 orders에 있는 order_id 중 조인이 수행되지 않은 컬럼이 있는지 체크해 보기로 했다.

새로 추가된 컬럼들 중 어느 하나라도 null인 경우를 마스크로 만들어 merge2에 적용했더니 4개의 order_id가 발견되었다. order_status가 canceled, 즉 취소된 주문인 경우가 3건 있고 delilvered라고 되어있는데 결제정보가 담기지 않은 주문 1건이다. 건수가 많지 않기도 하고 단순 오류일 가능성이 있어 이 행은 버리기로 했다.

확인 결과 기준이 되는 orders 테이블과 payments 테이블의 row 수의 차이가 딱 4개였는데, merge2를 만들 때 payment 정보를 못 불러온(즉, null인 경우) 경우의 수와 일치했다. 저 행들을 정리하면 merge2까지의 생성은 무사히 완료될 것 같다.

merge2에서 payment 정보를 못 불러온 행들을 제외시킨 결과, 99,437건으로 row가 정리되었다. inner join을 썼으면 처음부터 이런 결과를 얻을 수 있었겠지만, 몇 개의 row가 무슨 이유로 제외되는지를 정확하게 확인하고 넘어가야 해서 일부러 join의 방식은 left로 선택했다.

이제 테이블 merge 과정에서 2개를 완료했고,
merge2와 customers 테이블을 병합해서 merge3를 만든 후
맨 처음 만들었던 merge1과 최종적으로 병합하면 데이터셋이 완성될 것 같다.
이후 진행과정은 내일 TIL에서 다뤄보기로.

※ orders와 payments2_groupby를 inner 조건으로 한 방에 합친 결과

profile
기본기를 소홀히 하지 말자

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN