728x90
반응형

영화 데이터 전처리 및 시각화

1. 외부파일 읽어들이기

  • 제목 : "title" , 평점 : "score", 리뷰 : "comment", 긍정/부정 : "label"
### 라이브러리 정의
# - 행렬데이터 처리 라이브러리
import pandas as pd

file_path = "./data/movie_reviews.txt"
df_org = pd.read_csv(file_path,
                     ### 구분자 알려주기
                    delimiter = "\t",
                    names=["title", "score", "comment", "label"])
df_org

2. 데이터 전처리

🌳데이터 정보 확인 : 결측치 확인

df_org.info()


🌳 기초통계 확인 : 이상데이터 확인

df_org.describe()


🌳 평점(score) 현황데이터 확인

df_org["score"].value_counts()​


🌳중복데이터 확인하기

  • keep=False : 중복된 모든행 체크(중복이 있으면 True, 없으면 False)
df_org[df_org.duplicated(keep=False) == True]


🌳중복 데이터 추출하기

  • 중복 중에 하나는 제외하고 나머지 중복만 추출
df_del = df_org[df_org.duplicated() == True]
len(df_org[df_org.duplicated() == True])


🌳중복제거하기

df_new = df_org.drop_duplicates()
len(df_new)
df_new.info()

 

 

 

 

중복된 데이터 제거로 행수가 4073에서 4033으로 줄어들었다.

 

 

 

 

 

 

 

3. 데이터 탐색하기

🌳영화 제목만 추출하기

df_new["title"].unique()


🌳영화 제목별 리뷰갯수 현황 확인하기

df_new["title"].value_counts()


🌳각 영화별 평점 기초통계 확인하기

  • 영화제목별 평점에 대한 그룹집계하기
movie_info = df_new.groupby("title")["score"].describe()​

 

  • 기초통계 행단위 데이터 내림차순 정렬하기
movie_info = movie_info.sort_values(by=["count"], axis=0, ascending=False)
movie_info

 

4. 데이터 시각화

🌳 라이브러리

### 시각화 라이브러리
import matplotlib.pyplot as plt

### 폰트 설정 라이브러리
from matplotlib import font_manager, rc

### 한글폰트 설정
plt.rc("font", family="Malgun Gothic")

### 마이너스 기호 설정
plt.rcParams["axes.unicode_minus"] = False

 

 

5. 영화별 평점 평균 시각화 (1) - 막대그래프 

영화별 별점 평균이 가장 큰 영화는 orange 색으로, 나머지는 lightgrey 색으로 표현

🌳 라이브러리

### 평점 평균 계산을 위해 사용
import numpy as np

🌳 영화제목을 리스트 타입으로 받아오기

  •  array()
    • 넘파이 (numpy)에서 사용하는 배열(파이썬의 리스트와 동일)
    • 단, 하나의 타입만 저장 가능하다.
    • 이외 사용법은 파이썬의 리스트와 동일하다
  • unique() : numpy의 배열 타입으로 반환함
movie_title = df_new["title"].unique()
movie_title

  • tolist() : 파이썬의 리스트 타입으로 변환하는 함수
movie_title = df_new["title"].unique().tolist()
movie_title


🌳 영화별 평점 평균 추출하기

### 영화별 평점 평균 추출하기
# - 평점평균값을 저장할 딕셔너리 변수 선언
avg_score = {}

for m_title in movie_title:
    ### 평점 평균 계산하여 딕셔너리에 넣가
    avg = df_new[df_new["title"] == m_title]["score"].mean()
    # print(f"평점평균 : {avg}")

    ### 딕셔너리에 담기
    # key는 제목, value는 평점평균값
    avg_score[m_title] = avg

print(f"딕셔너리 최종값 : {avg_score}")


🌳 영화별 평점 평균 시각화

  • 컬러값 지정하기
    • array_str() : 문자열로 변환하는 함수
    • where() : 파이썬에서 if문과 동일한 조건문
    • where(조건, 참, 거짓) : 조건이 참이면 첫번째 값, 거짓이면 두번째 값 처리
# - 그래프 너비와 높이 지정
plt.figure(figsize=(10, 5))

# - 그래프 제목
plt.title("영화별 평점 평균 막대그래프", fontsize=17, fontweight="bold")

### 각 영화별 평점 평균 막대그래프 그리기
for k, v in avg_score.items() :
    color = np.array_str(np.where(v==max(avg_score.values()), "orange", "lightgrey"))
    #print(color)

    ### 막대 그래프 그리기
    plt.bar(k, v, color=color)

    ### 막대 그래프 상단에 평점평균 텍스트 표시하기
    plt.text(k, v, "%.2f"%v, horizontalalignment="center", verticalalignment="bottom")
    
### x축과 y축 제목 넣기
plt.xlabel("영화제목", fontweight="bold")
plt.ylabel("평점평균", fontweight="bold")

### x축의 값 각도 조절하기
plt.xticks(rotation = 75)

### 그래프를 이미지로 저장시키기
# - savefig() 함수는 plt.show() 전에 수행되어야 한다.
plt.savefig("./img/영화별 평점 평균 막대그래프.png")

plt.show()

 

6. 영화별 평점 평균 시각화 (2) - 점(분포) 그래프 그리기

🌳각 영화별 평점 분포도 그리기

      • 하나의 큰 그래프 안에 10개의 그래프를 넣어서 표현 → 이를 subplot 이라고 칭한다.
      • <이해를 위한 코드 설명 1>  
        • fig, axs = plt.subplots(5, 2, figsize=(15, 25)) : 그림(fig)과 5x2 그리드로 구성된 축(axs)을 만든다.
        • axs.flatten() : 5x2 그리드를 1차원 배열로 펼친다. 이렇게 하면 단일 루프에서 각 서브플롯을 반복하는 것이 더 쉬워진다.
        • avg_score.keys() 및 avg_score.values() : 나타낸 데이터 집합을 반복한다. 각 반복은 다른 영화 제목에 해당한다.
        • num_reviews = len(df_new[df_new["title"] == title]) : 현재 영화 제목의 리뷰 수를 계산한다.
        • x = np.arange(num_reviews) : 0에서 num_reviews - 1까지의 x값으로 이루어진 배열을 생성한다.
        • y = df_new[df_new["title"] == title]["score"] : DataFrame에서 현재 영화 제목의 평점을 추출한다.
        • subtitle = f"{title} ({num_reviews}명)" : 현재 서브플롯의 제목으로 영화 제목과 리뷰 수를 나타내는 부제목을 생성한다.
        • ax.set_title(subtitle, fontsize=15, fontweight="bold") : 서브플롯의 제목을 설정한다.
        • ax.plot(x, y, "o") : 점으로 평점을 리뷰 인덱스에 대해 나타내는 산점도를 생성한다.
        • ax.axhline(avg, color="red", linestyle="--") : 영화의 평균 평점을 나타내는 수평 대시된 선을 추가한다.
      • <이해를 위한 코드 설명 2>
        • fig, axs = plt.subplots(5, 2, figsize=(15, 25))
          서브플롯 함수 사용해서 큰 그래프 하나에 작은 그래프들을 넣으려는 작업을 함
          작은 그래프들을 몇행 몇열로 넣을 건지 지정 해야한다. 지금 코드는 5행 2열. figsize는 전체 크기
        • axs = axs.flatten() 
          for문을 사용하기 위해 flatten으로 틀 정렬 >> 이건 문법 적인 것
        • for title, avg, ax in zip(avg_score.keys(), avg_score.values(), axs)
          avg_score는 영화 제목을 키로, 해당 영화의 평균 평점을 값으로 가지는 딕셔너리로 avg_score.keys()는 딕셔너리의 키들을, avg_score.values()는 딕셔너리의 값들을 반환한다.

            axs는 5행 2열의 subplot들을 1차원 배열로 평탄화한 것이다. 따라서 zip(avg_score.keys(), avg_score.values(), axs)는 영화 제목, 평균 평점, 그리고 subplot을 하나의 튜플로 묶어 반환한다.
        • df_new[df_new["title"] == title]
          데이터프레임 df_new에서 영화 제목이 title인 행들을 선택하는 부분이다. len(df_new[df_new["title"] == title])은 해당 영화의 리뷰 개수를 반환한다.
          따라서 for title, avg, ax in zip(avg_score.keys(), avg_score.values(), axs):에서 각각의 반복마다 title에는 영화 제목, avg에는 해당 영화의 평균 평점, ax에는 subplot이 할당된다. 그리고 num_reviews에는 해당 영화의 리뷰 개수가 할당된다.
        • x = np.arange(num_reviews), y = df_new[df_new["title"] == title]["score"]
          x,y축을 지정해 주려는데 y는 평점이므로 0에서 10까지 범주가 정해져 있음
          그러나 x축은 갯수. 갯수는 데이터가 없음. 그렇기 때문에 전체 갯수를 뽑고 0부터 순차적으로 뽑아 냄
          →그려진 첫번째 그래프에선 500씩 범주를 알아서 잘라냄
          arange로 순차적인 리스트 값으로 반환 (일정한 범위로 쪼개준다)
          x,y는 범주형 데이터
        • ax.set_title
          해당공간(ax)의 제목 지정
        • ax.plot(x, y, "o")
          공간에 그래프 그린다.
        • ax.axhline(avg, color="red", linestyle="--")
          ax.axhline은 서브플롯 안에 또다른 그래프를 그리는 것 위에 것과 상관없음
          avg 수평선을 기준으로 y축값을 넣어서 나타낸다
    •  
# - 5행 2열의 subplot 생성하여 구현하기
# - subplots(행 갯수, 열 갯수, 전체 그래프 크기)
# - fig : 큰 그래프 정보
# - axs : 5행 2열의 내부 그래프 공간 정보
fig, axs = plt.subplots(5, 2, figsize=(15, 25))

### 여러개의 그래프를 for문을 이용해서 표현하고자 할 떼 아래 먼저 수행
# - flatten() : 틀 정렬하기 - > 5행 2열의 틀을 정렬해 놓기
axs = axs.flatten()

### 각 그래프를 행렬 공간의 subplot에 넣기
for title, avg, ax in zip(avg_score.keys(), avg_score.values(), axs):
    # print(f"{title} / {avg} / {ax}")
    
    ### x축에는 영화 리뷰 갯수, y축에는 평점 평균
    ### 리뷰 갯수 추출하기
    num_reviews = len(df_new[df_new["title"] == title])
    # - arange(num) : 0부터 num까지의 값을 순차적으로 만들기
    x = np.arange(num_reviews)
    # print(f"x ---------> {x}")

    ### y축에는 평점 추가
    y = df_new[df_new["title"] == title]["score"]
    # print(f"y ----------> {y}")

    ### 각 그래프에 제목 넣기
    subtitle = f"{title} ({num_reviews}명)"
    ax.set_title(subtitle, fontsize=15, fontweight="bold")
    

    ### 점 그래프 그리기
    # - "o" : 점으로 표현하는 마커 기호
    ax.plot(x, y, "o")


    ### 각 영화별 평점평균을 빨강색 점선으로 표시하기
    # - axhline() : 각 subplot 공간에 수평선 그리기
    ax.axhline(avg, color="red", linestyle="--")

plt.savefig("./img/각 영화별 평점 분포도.png")

plt.show()

728x90
반응형
728x90
반응형

영화 데이터수집 웹크롤링

크롤링 Crawling
소프트웨어 따위가 웹을 돌아다니며 유용한 정보를 찾아 특정 데이터베이스로 수집해 오는 작업. 또는 그러한 기술

다음 영화 사이트 웹크롤링

  • URL : http://movie.daum.net
  • 다음영화 > 랭킹 > 박스오피스 > 월간 위치의 데이터 수집
  • 수집데이터 : 영화제목, 평점, 댓글
  • 생성할 데이터 : 긍정/부정 ( 별점 점수 별 긍정인지 부정인지 판단하는 데이터 생성 )
  • 프로그램에서 https://movie.daum.net/ranking/boxoffice/monthly 주소를 열어서 크롬에서 제어

웹크롤링 라이브러리

  • 정적인 웹크롤링을 할 경우
    • BeautifulSoup : 하나의 페이지에 보이는 부분만 수집할 때 사용
  • 동적인 웹크롤링을 할 경우
    • selenium : 클릭과 같은 이벤트 등 페이지 전환을 하면서 수집할 때 사용
  • 동적 웹페이지 처리를 위한 라이브러리
    • Prompt에서 설치 필요 : pip install selenium
    • from selenium import webdriver
    • webdriver : 브라우저 자체를 컨트롤 함
  • 웹페이지 내에 데이터 추출을 위한 라이브러리
    • from selenium.webdriver.common.by import By
    • By : 페이지 안에 있는 html을 컨트롤 함
  • 시간 라이브러리
    • import time
    • 웹브라우저에 접근 할 때 페이지에 클릭이라는 동적 이벤트를 발생시킨다. 이때  네트워크 사양에 따라 느리게 실행 될 수 있고  다른 pc보다 빠르게 실행 될 수 있다. 즉, 사양에 따라 페이지 로딩 시간이 다르게 나타난다다. 그 로딩시간에 다른 행동을 하지 못하도록 즉, 외부에서 읽어들일 수 있는 시간을 주기 위해 time 사용

1. 크롬 브라우저 띄우기

  • 브라우저 컨트롤
driver = webdriver.Chrome()
  • url을 이용하여 페이지 접근
    • get() : 페이지에 접근 후 해당 html 코드 읽어 들이기
    • driver 객체가 모든 정보를 가지고 있음
driver.get("https://movie.daum.net/ranking/boxoffice/monthly")

 

2. 제목이 있는 부분의 html 태그 경로(패스) 추출하기

  • 크롬브라우저 → F12(개발자도구) → 영화제목 마우스 우클릭 → [검사] 클릭 → a 태그에 마우스 위치 후 우클릭 → copy → copy selector 클릭 → 해당 제목의 위치 저장

  • 영화 제목이 있는 a 태그 위치 경로 확인
    • li:nth-child(1)에서 :nth-child(1) 이걸 빼면 ol아래 모든 li가 해당 된다.
movie_path = "#mainContent > div > div.box_boxoffice > ol > li > div > div.thumb_cont > strong > a"

 

  • 현재 크롬브라우저에 보이는 영화제목 모두 추출하기
    • find_element() : 한건 조회
    • find_elements() : 여러건 조회(리스트 타입으로 반환)
    • By.CSS_SELECTOR : CSS 스타일 경로를 인식할 수 있도록 지정
   movie_elements = driver.find_elements(By.CSS_SELECTOR, movie_path)
    print(f"movie_elements length = {len(movie_elements)}")
    # 리스트 타입 0번째 제목 , 태그와 태그 사이에 text가 들어있다.( .text로 접근 )
    print(f"title[0] =>> {movie_elements[0].text}")
    print(f"movie_elements(제목) = {movie_elements}")

 

  • 웹크롤링 처리가 모두 완료되면, driver 종료해야 한다
driver.quit()
  •  try - except로 처리하기
try:
    driver = webdriver.Chrome()

    driver.get("https://movie.daum.net/ranking/boxoffice/monthly")

    movie_path = "#mainContent > div > div.box_boxoffice > ol > li > div > div.thumb_cont > strong > a"
    
    movie_elements = driver.find_elements(By.CSS_SELECTOR, movie_path)
    print(f"movie_elements length = {len(movie_elements)}")
    print(f"title[0] =>> {movie_elements[0].text}")
    print(f"movie_elements(제목) = {movie_elements}")

except Exception as e:
    print(e)
    driver.quit()

finally:
    driver.quit()

 

 

3. Click ( ) 이벤트 발생시키기 (1) - 상세 페이지

아래 코드는 모두 for i 문 안에 작성된 코드

  • 제목을 클릭 시켜서 상세 페이지로 이동하기
    • 마우스로 제목을 클릭하는 행위와 동일한 코드
    • click() 이벤트 발생
# 제목 10개만 추출하기
    for i in range(10) : 
        title = movie_elements[i].text.strip()
        print(f"No[{i}] / title[{title}] Start----------------")

        ### 제목을 클릭 시켜서 상세 페이지로 이동하기
        movie_elements[i].click()

 

  • 상세페이지로 접근했다라는 정보를 받아오기
    • 실제 상세페이지에 접근
    • window_handles : 페이지가 열릴때 마다 리스트타입으로 윈도우 정보를 순서대로 가지고 있는 객체이다. -1 은 마지막에 접근한 페이지를 의미한다.
      movie_handle = driver.window_handles[-1]
        # - 새로 열린 페이지로 전환하기
        driver.switch_to.window(movie_handle)

 

  • 페이지 로딩 및 코드 읽어들이는 시간을 벌어주기
time.sleep(1)

 

→ 여기까지 실행하면 웹 페이지 열리고 영화 '서울의 봄' 상세페이지까지 들어갔다가 종료됨


4. Click ( ) 이벤트 발생시키기 (2) - [평점] 

  • [평점] 탭 클릭 이벤트 발생 시키기

   tap_score_path = "#mainContent > div > div.box_detailinfo > div.tabmenu_wrap > ul > li:nth-child(4) > a"

 

  • a태그 정보 가지고 오기
 tap_score_element = driver.find_element(By.CSS_SELECTOR, tap_score_path)

 

  • [평점] 탭, 즉 a태그 클릭 이벤트 발생시키기[평점] 페이지로 접근했다라는 정보를 받아오기
	tap_score_handle = driver.window_handles[-1]
        # - 새로 열린 페이지로 전환하기
        driver.switch_to.window(tap_score_handle)

 

  • 페이지 로딩 및 코드 읽어들이는 시간을 벌어주기
        time.sleep(1)

 

5. 모든 평점 / 리뷰 데이터 추출하기

  • 모든 평점 데이터 추출하기
        score_path = "ul.list_comment div.ratings"
        score_lists = driver.find_elements(By.CSS_SELECTOR, score_path)
        print(f"평점 갯수 : {len(score_lists)}")

 

  • 모든 리뷰 데이터 추출하기
        comment_path = "ul.list_comment p.desc_txt"
        comment_lists = driver.find_elements(By.CSS_SELECTOR, comment_path)
        print(f"리뷰 갯수 : {len(comment_lists)}")

 

6. 평점을 이용하여 긍정 / 부정 값 생성하기

  • 평점 또는 리뷰 데이터가 없을 수 있기에 두개 리스트의 갯수 중 작은 값을 사용
  • 평점 또는 리뷰가 없으면, 수집에서 제외
  • 기존의  for j in range(len(score_lists))으로 for문을 돌릴 때, 리뷰가 없을 경우 len(comment_lists)는  len(score_lists)와 길이가 다르기 때문에 오류가 난다.
for_cnt = 0
        if len(score_lists) < len(comment_lists) :
            for_cnt = len(score_lists)
        elif len(score_lists) > len(comment_lists) :
            for_cnt = len(comment_lists)
        else :
            for_cnt = len(score_lists)

 

아래 코드는 모두 for j 문 안에 작성된 코드

  • 평점, 리뷰 추출하기
 for j in range(for_cnt) : 
            ### 평점 추출하기
            score = score_lists[j].text.strip()
            
            ### 리뷰 추출하기
            comment = comment_lists[j].text.strip().replace("\n", "")
            
            print(f"{title}  \t{score}  \t{comment} \n")

 

  • 평점을 이용해서 긍정/ 부정 데이터 생성
    • 긍정 : 평점이 8이상인 경우로, 긍정값은 1 사용
    • 부정 : 평점이 4이하인 경우로, 부정값은 0 사용
    • 기타 : 나머지, 기타값은 2 사용
   label = 0
            if  int(score) >= 8 :
                label = 1
            elif int(score) <= 4 :
                label = 0
            else :
                label = 2
                
    print(f"{title}  \t{score}  \t{comment} \t{label} \n")

 

7. 다시 메인으로 이동

아래 코드는 for j 문 밖에 작성된 코드

  • 영화 한편에 대한 정보수집이 끝나면 다시 메인으로 이동
  • execute_script() : 자바스크립트 문법 처리 함수
	driver.execute_script("window.history.go(-2)")
        time.sleep(1)

 

→ 여기까지 실행하면 영화 10개의 제목, 평점, 리뷰, 긍정/부정 데이터가 출력된다

 

8. 수집 데이터 저장하기

  • 수집데이터 txt 파일로 저장시키기 ( 아래 코드는 for i 문 위에 작성된 코드)
 f = open("./data/movie_reviews.txt", "w", encoding="UTF-8")
  • 파일에 쓰기  ( 아래 코드는 for j문 에 작성된 코드)
  f.write(f"{title}\t{score}\t{comment}\t{label}\n")
  • 파일 자원 닫기 (아래 코드는 except 문에 작성된 코드)
f.close()

9. 모두 펼치기(더보기) 수행한 뒤 데이터 추출

아래 코드는 for i 문 안에서 모든 평점 데이터 추출하기 전에 작성된 코드

  • [평점] 더보기 버튼 클릭하여 모든 평점 보이게 펼치기
  • 처음에 실행했다가 오류난 이유는 영화의 평점 중에 리뷰가 적히지 않은게 있었음. 6번으로 가서  if 문 추가.
	### - 펼친 갯수 확인 변수
        more_view_cnt = 0

        ### 모두 펼치기(더보기) 수행
         while True :
            try: 
                more_view_path = "#alex-area > div > div > div > div.cmt_box > div.alex_more > button"
                more_view_element = driver.find_element(By.CSS_SELECTOR, more_view_path)

                more_view_element.click()
                
                ### 상세페이지로 접근했다라는 정보를 받아오기
                movie_handle = driver.window_handles[-1]
                # - 새로 열린 페이지로 전환하기
                driver.switch_to.window(movie_handle)
                ### 페이지 로딩 및 코드 읽어들이는 시간을 벌어주기
                time.sleep(1)

                ### 임시로 2번만 반복 처리 후 break 처리
                # if more_view_cnt == 2 :
                #   break
                
                ### 더보기 클릭 횟수 확인을 위해 1씩 증
                more_view_cnt += 1
                
            except Exception as e :
                ### 더이상 더보기 버튼이 보이지 않으면 오류 발생
                # - 오류 발생 시점이 더보기 버튼이 끝나는 시점
                break

        ### 더보기 클릭획수 확인하기
        print(f"더보기 클릭 횟수 : [{more_view_cnt}]")

 

→ 여기까지 실행하면 영화 10개의 제목과  더보기를 눌러 모두 펼쳤을 때의 평점, 리뷰, 긍정/부정 데이터가 출력된다

728x90
반응형
728x90
반응형

문제 1 - 자동차 대여 기록에서 장기/단기 대여 구분하기

1. 문제 

https://school.programmers.co.kr/learn/courses/30/lessons/151138

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

  • DATEDIFF () : 두개의 날짜값의 차이를 int로 반환하는 내장함수이다.
  • 마자막 날짜에서 시작 날짜의 차이를 구하면 하루를 포함하지 않는 값이 나오므로 +1 해줘야 한다.
SELECT HISTORY_ID
        , CAR_ID
        , DATE_FORMAT(START_DATE, "%Y-%m-%d") AS START_DATE
        , DATE_FORMAT(END_DATE, "%Y-%m-%d") AS END_DATE
        , (CASE 
            WHEN (DATEDIFF(END_DATE, START_DATE) + 1) >= 30 THEN "장기 대여"
            ELSE "단기 대여"
            END) as RENT_TYPE
from CAR_RENTAL_COMPANY_RENTAL_HISTORY
where START_DATE like '2022-09%'
order by HISTORY_ID desc

 

문제 2 - 특정 옵션이 포함된 자동차 리스트 구하기

1. 문제 

https://school.programmers.co.kr/learn/courses/30/lessons/157343

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT CAR_ID, CAR_TYPE, DAILY_FEE, OPTIONS
from CAR_RENTAL_COMPANY_CAR
where OPTIONS LIKE '%네비게이션%'
order by CAR_ID DESC

 

문제 3 - 과일로 만든 아이스크림 고르기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/133025

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT A.FLAVOR
FROM FIRST_HALF A, ICECREAM_INFO B
WHERE (A.FLAVOR = B.FLAVOR 
       AND B.INGREDIENT_TYPE = "fruit_based"
 AND A.TOTAL_ORDER > 3000)
ORDER BY A.TOTAL_ORDER DESC

 

문제 4 - 인기있는 아이스크림

1. 문제 

https://school.programmers.co.kr/learn/courses/30/lessons/133024

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT FLAVOR
FROM FIRST_HALF
ORDER BY TOTAL_ORDER DESC, SHIPMENT_ID

 

문제 5 - 흉부외과 또는 일반외과 의사 목록 출력하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/132203

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT DR_NAME
        , DR_ID
        , MCDP_CD
        , DATE_FORMAT(HIRE_YMD, '%Y-%m-%d') AS HIRE_YMD
FROM DOCTOR
where MCDP_CD IN ("CS", "GS")
order by HIRE_YMD desc, DR_NAME

 

문제 6 - 12세 이하인 여자 환자 목록 출력하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/132201

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT PT_NAME,	PT_NO, GEND_CD,	AGE, IFNULL(TLNO, "NONE") AS TLNO
FROM PATIENT
WHERE GEND_CD = "W"
    AND AGE <= 12
ORDER BY AGE DESC, PT_NAME

 

문제 7 - 가장 비싼 상품 구하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/131697

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT MAX(PRICE) AS MAX_PRICE
FROM PRODUCT

 

문제 8 - 조건에 맞는 회원수 구하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/131697

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT COUNT(USER_ID) AS USERS
FROM USER_INFO
WHERE JOINED LIKE "2021%"
    AND AGE BETWEEN 20 AND 29

 

문제 9 - 나이 정보가 없는 회원 수 구하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/131528

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT COUNT(USER_ID) AS USERS
FROM USER_INFO
WHERE AGE IS NULL

 

문제 10 - 경기도에 위치한 식품창고 목록 출력하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/131114

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT WAREHOUSE_ID, WAREHOUSE_NAME, ADDRESS, IFNULL(FREEZER_YN, "N")
FROM FOOD_WAREHOUSE
WHERE ADDRESS LIKE "경기도%"
ORDER BY  WAREHOUSE_ID

 

문제 11 - 강원도에 위치한 생산공장 목록 출력하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/131112

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT FACTORY_ID, FACTORY_NAME, ADDRESS
FROM FOOD_FACTORY
WHERE ADDRESS LIKE "강원도%"
ORDER BY FACTORY_ID

 

문제 12 - 경기도에 위치한 식품창고 목록 출력하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59415

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT MAX(DATETIME) AS 시간
FROM ANIMAL_INS

 

문제 13 - 이름이 있는 동물의 아이디

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59407

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
ORDER BY ANIMAL_ID

 

문제 14 - 상위 n개 레코드

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59405

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

  • MySQL에서 상위 N개의 데이터를 출력하기 위해선 LIMIT N 함수를 사용한다.
  • ORACLE : ROWNUM < N
  • MS - SQL : TOP N, TOP N WITH TIES 
SELECT NAME
FROM ANIMAL_INS
ORDER BY DATETIME
LIMIT 1

 

문제 15 - 여러 기준으로 정렬하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59404

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID, NAME, DATETIME
FROM ANIMAL_INS
ORDER BY NAME, DATETIME DESC

 

문제 16 - 동물의 아이디와 이름

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59403

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
ORDER BY ANIMAL_ID

 

문제 17- 이름이 없는 동물의 아이디

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59039

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NULL
ORDER BY ANIMAL_ID

 

문제 18 - 어린 동물 찾기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59037#fn1

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
WHERE INTAKE_CONDITION != "Aged"
ORDER BY ANIMAL_ID

 

문제 19 - 아픈 동물 찾기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59036

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID, NAME
from ANIMAL_INS
where INTAKE_CONDITION = "Sick"
order by ANIMAL_ID

 

문제 20 - 역순 정렬하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59035

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT NAME, DATETIME
FROM ANIMAL_INS
ORDER BY ANIMAL_ID DESC

 

문제 21 - 모든 레코드 조회하기

1. 문제

https://school.programmers.co.kr/learn/courses/30/lessons/59034

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2. 작성한 코드

SELECT ANIMAL_ID
        , ANIMAL_TYPE
        , DATETIME
        , INTAKE_CONDITION
        , NAME
        , SEX_UPON_INTAKE
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
728x90
반응형
728x90
반응형

📝 문제

더보기

1. 문제 설명

 코니는 영어 단어가 적힌 카드 뭉치 두 개를 선물로 받았습니다. 코니는 다음과 같은 규칙으로 카드에 적힌 단어들을 사용해 원하는 순서의 단어 배열을 만들 수 있는지 알고 싶습니다.

  • 원하는 카드 뭉치에서 카드를 순서대로 한 장씩 사용합니다.
  • 한 번 사용한 카드는 다시 사용할 수 없습니다.
  • 카드를 사용하지 않고 다음 카드로 넘어갈 수 없습니다.
  • 기존에 주어진 카드 뭉치의 단어 순서는 바꿀 수 없습니다.

예를 들어 첫 번째 카드 뭉치에 순서대로 ["i", "drink", "water"], 두 번째 카드 뭉치에 순서대로 ["want", "to"]가 적혀있을 때 ["i", "want", "to", "drink", "water"] 순서의 단어 배열을 만들려고 한다면 첫 번째 카드 뭉치에서 "i"를 사용한 후 두 번째 카드 뭉치에서 "want"와 "to"를 사용하고 첫 번째 카드뭉치에 "drink"와 "water"를 차례대로 사용하면 원하는 순서의 단어 배열을 만들 수 있습니다.

문자열로 이루어진 배열 cards1, cards2와 원하는 단어 배열 goal이 매개변수로 주어질 때, cards1과 cards2에 적힌 단어들로 goal를 만들 있다면 "Yes"를, 만들 수 없다면 "No"를 return하는 solution 함수를 완성해주세요.


2. 제한사항

  • 1 ≤ cards1의 길이, cards2의 길이 ≤ 10
    • 1 ≤ cards1[i]의 길이, cards2[i]의 길이 ≤ 10
    • cards1과 cards2에는 서로 다른 단어만 존재합니다.
  • 2 ≤ goal의 길이 ≤ cards1의 길이 + cards2의 길이
    • 1 ≤ goal[i]의 길이 ≤ 10
    • goal의 원소는 cards1과 cards2의 원소들로만 이루어져 있습니다.
  • cards1cards2goal의 문자열들은 모두 알파벳 소문자로만 이루어져 있습니다.

3. 입출력 예

cards1 cards2 goal result
["i", "drink", "water"] ["want", "to"] ["i", "want", "to", "drink", "water"] "Yes"
["i", "water", "drink"] ["want", "to"] ["i", "want", "to", "drink", "water"] "No"

 


4. 입출력 예 설명

  • 입출력 예 #1
     본문과 같습니다.
  • 입출력 예 #2
     cards1에서 "i"를 사용하고 cards2에서 "want"와 "to"를 사용하여 "i want to"까지는 만들 수 있지만 "water"가 "drink"보다 먼저 사용되어야 하기 때문에 해당 문장을 완성시킬 수 없습니다. 따라서 "No"를 반환합니다.

📝 작성한 코드

 처음엔 HaspMap을 써보려다 실패하고 작성한 코드인데, 다른 분들이 작성한 걸 보니 HashMap을 써서 푸신 분이 꽤나 있었다. 이 외에도 Queue(큐)라는 클래스를 사용하신 분들이 많았다. 큐는 선입선출법의 성격을 지닌 자료구조라고 한다. 문제의 조건에서 카드 뭉치를 순서대로 한장 씩 사용한다고 했으므로, 큐의 성격을 이용하여 문제를 풀어보면 좋을 것 같다.
 
 내가 작성한 코드는 간단히 for문을 돌려 진행되는 코드이다. 처음에는 goal[i]와 cards[i]를 비교해보면 된다고 생각했는데, 인덱스범위 오류가 나는 걸 보고 cards의 길이는 goal의 길이보다 작다는 것을 깨달았다. 그래서 cards의 인덱스 값을 따로 변수 (card1Index)로 주고 cards1[card1Index]이 goal에 들어있는 값과 일치하면 그 인덱스에 해당하는 cards의 값은 다시 사용할 수 없도록 +1을 해주었다. 그리고 answer의 초기값을 Yes로 설정해두고 if문의 조건1과 2가 제대로 실행되않으면 No를 반환하도록 하였다.
 

class Solution {
    public String solution(String[] cards1, String[] cards2, String[] goal) {
        String answer = "Yes";
        int card1Index = 0;
        int card2Index = 0;
 
        for (int i = 0; i < goal.length; i++) {
            if (card1Index < cards1.length && cards1[card1Index].equals(goal[i])){
                card1Index++;
            }
            else if(card2Index < cards2.length && cards2[card2Index].equals(goal[i])){
                card2Index++;
            }
            else {
                answer = "No";
            }
        }    
                 
        return answer;
    }
}

 

📝 공부해 볼 코드

Queue(큐)를 사용한 코드.
큐는 데이터를 일시적으로 쌓아두기 위한 자료구조로 스택과는 다르게 FIFO(First In First Out)의 형태를 가진다. FIFO 형태는 뜻 그대로 먼저 들어온 데이터가 가장 먼저 나가는 선입선출을 의미한다.
 
<코드 설명>

  1. Queue<String> queue1 및 Queue<String> queue2를 생성하고, 각각 cards1 및 cards2 배열을 큐로 변환하여 초기화
  2. for (String g : goal) 루프를 통해 목표 배열을 순회한다. 루프 내에서는 peek()라는 메서드를 통해 현재 목표 g가 queue1 또는 queue2의 맨 앞에 있는 카드와 일치하는지 확인한다.
  3. * queue.peek(); // queue의 첫번째 값 참조
  4. 만약 g가 queue1의 맨 앞에 있는 카드와 일치한다면, queue1.poll()를 사용하여 큐에서 해당 카드를 제거한다. 이어서 continue 키워드를 사용하여 다음 목표로 이동한다.
  5. * queue.poll(); // queue에 첫번째 값을 반환하고 제거 비어있다면 null
  6. 만약 g가 queue2의 맨 앞에 있는 카드와 일치한다면, queue2.poll()를 사용하여 큐에서 해당 카드를 제거한다. 이어서 continue 키워드를 사용하여 다음 목표로 이동한다.
  7. 만약 g가 어느 큐에도 일치하지 않는다면, "No"를 반환하며 프로그램을 종료시킨다
  8. 모든 목표를 순회한다면, "Yes"를 반환한다.
import java.util.*;

class Solution {
    public String solution(String[] cards1, String[] cards2, String[] goal) {
        String answer = "";

        Queue<String> queue1 = new LinkedList<>(Arrays.asList(cards1));
        Queue<String> queue2 = new LinkedList<>(Arrays.asList(cards2));

        for (String g : goal) {
            if (g.equals(queue1.peek())) {
                queue1.poll();
                continue;
            } else if (g.equals(queue2.peek())) {
                queue2.poll();
                continue;
            }
            return "No";
        }

        return "Yes";
    }
}

 

Queue(큐) 참고 주소
https://coding-factory.tistory.com/602

728x90
반응형
728x90
반응형

📝문제

더보기

1. 문제 설명

과일 장수가 사과 상자를 포장하고 있습니다. 사과는 상태에 따라 1점부터 k점까지의 점수로 분류하며, k점이 최상품의 사과이고 1점이 최하품의 사과입니다. 사과 한 상자의 가격은 다음과 같이 결정됩니다.

  • 한 상자에 사과를 m개씩 담아 포장합니다.
  • 상자에 담긴 사과 중 가장 낮은 점수가 p (1 ≤ p ≤ k)점인 경우, 사과 한 상자의 가격은 p * m 입니다.

과일 장수가 가능한 많은 사과를 팔았을 때, 얻을 수 있는 최대 이익을 계산하고자 합니다.(사과는 상자 단위로만 판매하며, 남는 사과는 버립니다)

예를 들어, k = 3, m = 4, 사과 7개의 점수가 [1, 2, 3, 1, 2, 3, 1]이라면, 다음과 같이 [2, 3, 2, 3]으로 구성된 사과 상자 1개를 만들어 판매하여 최대 이익을 얻을 수 있습니다.

  • (최저 사과 점수) x (한 상자에 담긴 사과 개수) x (상자의 개수) = 2 x 4 x 1 = 8

사과의 최대 점수 k, 한 상자에 들어가는 사과의 수 m, 사과들의 점수 score가 주어졌을 때, 과일 장수가 얻을 수 있는 최대 이익을 return하는 solution 함수를 완성해주세요.

제한사항

  • 3 ≤ k ≤ 9
  • 3 ≤ m ≤ 10
  • 7 ≤ score의 길이 ≤ 1,000,000
    • 1 ≤ score[i] ≤ k
  • 이익이 발생하지 않는 경우에는 0을 return 해주세요.

2. 입출력 예

k m score result
3 4 [1, 2, 3, 1, 2, 3, 1] 8
4 3 [4, 1, 2, 2, 4, 4, 4, 4, 1, 2, 4, 2] 33

3. 입출력 예 설명

  • 입출력 예 #1
    문제의 예시와 같습니다.

  • 입출력 예 #2
    다음과 같이 사과 상자를 포장하여 모두 팔면 최대 이익을 낼 수 있습니다.
사과 상자가격
[1, 1, 2] 1 x 3 = 3
[2, 2, 2] 2 x 3 = 6
[4, 4, 4] 4 x 3 = 12
[4, 4, 4] 4 x 3 = 12

따라서 (1 x 3 x 1) + (2 x 3 x 1) + (4 x 3 x 2) = 33을 return합니다.

📝작성한 코드

1. 처음 작성한 코드

 과일장수가 최대의 이익을 얻기 위해선 m만큼 과일을 담을 때 담아지는 과일의 수가 딱 떨어지지 않아 1상자를 완성하지 못하고 남아버릴 경우 남아진 과일은 점수가 낮은 과일이여야 하므로 가장 점수가 높은 과일부터  담아져야 한다. 가격을 계산 할 때는 한 상자에 같은 점수의 과일이 담아져 있지 않은 경우 그 상자에서 가장 점수가 낮은 과일을 기준으로 가격을 계산해야 한다. 그렇기 때문에 점수를 오름차순으로 정렬하여 가장 첫번째 과일의 점수로 1상자의 가격을 계산해야 한다.

  • Arrays.sort(score) : score에서 큰 수 부터 차례대로 뽑아오기 위해 오름차순으로 정렬하였다.
  • for문 : score의 값들을 차례대로 뽑아오면서 계산을 하기 위해 for문을 작성했다. k = 4, m = 3, score = [4, 1, 2, 2, 4, 4, 4, 4, 1, 2, 4, 2] 라고 생각해보면 i의 처음 값은 -3이며 이 값은  -score.length보다 크거나 같으므로 -12보다 작아 질 수 없다. for문이 돌아가면 score[-3]인 4가 m인 3과 곱해지고 12라는 값이 answer에 들어간다. for문이 실행됐으니까 i -= m 이 실행 되면서 i는 -6이 된다.

하지만 실행하면 ArrayIndexOutOfBoundsException 오류가 떴다. 이 오류는 배열 인덱스가 배열 길이를 벗어나는 경우에 발생한다. 오류 메시지에서 "Index -3 out of bounds for length 12"가 나오는데, 이는 배열의 길이가 12이고 인덱스 -3에 접근하려고 하고 있다는 것을 의미한다. 

 즉, 자바에서 배열의 인덱스에 접근할 때 음수 사용이 불가능 하다는 것이다. 배열 인덱스는 음이 아닌 정수여야 한다. 그러나 나는  for문에서 i를 -m으로 주면서 score[-3]라는 코드가 나왔기 때문에 오류가 났던 것이다.

import java.util.*;

class Solution {
    public int solution(int k, int m, int[] score) {
        int answer = 0;
        Arrays.sort(score); 
        // System.out.println(Arrays.toString(score));   
        	// [1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4]
       
        for (int i = -m; i >= -score.length; i -= m){           
             answer += score[i] * m ;
        }
        
        return answer;
    }
}

 

2. 수정한 코드

 수정한 코드에서는 i를  score.length로 주었다. k = 4, m = 3, score =[4, 1, 2, 2, 4, 4, 4, 4, 1, 2, 4, 2]라고 생각해보면 i의 초기값은 12이고 i >= m만큼 반복 하므로 i는 3보다 크거나 같을 때까지 반복한다. for문이 실행되면 score[9] 인 4에 m인 3이 곱해져  12라는 값이 answer에 들어간다. for문이 실행됐으니까 i -= m 이 실행 되면서 i는 9가 된다.

 이렇게 조건을 주면 배열의 인덱스에 음수를 넣지 않고도 원하는 값을 가져올 수 있다.

import java.util.*;

class Solution {
    public int solution(int k, int m, int[] score) {
        int answer = 0;
        Arrays.sort(score); 
        // System.out.println(Arrays.toString(score));   
        	// [1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4]

        for(int i = score.length; i >= m; i -= m){
             answer += score[i - m] * m;
        }
 
        return answer;
    }
}

728x90
반응형
728x90
반응형

데이터 전처리 시각화

🐳 데이터 불러오기

### 라이브러리 정의하기
import pandas as pd

### 사용할 데이터 읽어오기
# 데이터프레임 변수명 : df_bus_card_tot
file_path = "./01_data/all/df_bus_card_tot.csv"
df_bus_card_tot = pd.read_csv(file_path)

print("갯수 : ", len(df_bus_card_tot))
df_bus_card_tot.head(1)

 

 

데이터 시각화

시각화의 목적?
직관적인 데이터 확인

 

🐳 시각화에 필요한 라이브러리

### 시각화 라이브러리
import matplotlib # 시각화 라이브러리를 쓰겠다!
import matplotlib.pyplot as plt # 차트나 그림이 들어있다
import seaborn as sns # 파스텔 색감

### 그래프 내에 한글이 포함된 경우 폰트 처리가 필요하다
# - 한글 꺠짐 방지를 위한 라이브러리
### 폰트 환결설정 라이브러리
from matplotlib import font_manager, rc

### 운영체제 확인을 위한 라이브러리
# import platform

plt.rc("font", family = "Malgun Gothic")

### 그래프 내에 마이너스(-) 표시 기호 적용하기
plt.rcParams["axes.unicode_minus"] = False

 

🐳 기준월 및 기준일자별 버스 이용량 시각화 분석

  • 사용할 컬럼 : 기준월, 기준일, 승객연령
  • 사용할 집계함수 : count
  • 이용량 집계를 위한 함수 : pivot_table()
    - 히트맵 시각화시 데이터 생성
  • 사용할 그래프 : 히트맵(hitmap)
    - 히트맵을 그리기 위한 데이터를 만들어야 하는데, 그 데이터를 만들어내는게 피벗테이블
df_pivot = df_bus_card_tot.pivot_table(index = "기준월",
                                       columns = "기준일",
                                       values = "승객연령",
                                       aggfunc = "count")

 

<데이터 count 집계하기>
- y축 : index
- x축 : columns
- 집계 : count(승객연령)

- 결측값 존재 : NaN

 

 

 

🐳 결측치(NaN) 처리하기

  • 결측치를 처리한 이유가 명확해야 한다.
    - 2월 결측치의 경우 2월이 28일 밖에 없어서 나머지 일에는 결측치가 나왔고 타당한 이유이기에 처리가 가능하다.
    - 3월 결측치의 경우 타당한 이유가 없이 결측치를 처리 할 수 없다. 이럴땐 데이터 제공자 측에 왜 데이터가 없는지 확인하고 타당한 이유 생성해야한다. 만약 버스시스템 오류로 인한 확인 불가라는 답변이 온다면 이유가 생성 됐다면 결측치를 0으로 처리하고 타당한 이유 작성하면 된다.
    - 0 으로 처리하지 않고 3월의 전체 평균을 삽입하거나 비어있는 값 전의 값과 후의 값의 평균을 넣거나 데이터를 아예 삭제하는 방법이 있다.
  • 모든 결측치(NaN)는 0으로 대체하기
df_pivot = df_pivot.fillna(0)
df_pivot

 

 

히트맵(heatmap) 시각화

🐳 기준일 및 기준일자별 버스 이용량 시각화 분석

### 그래프 전체 너비, 높이 설정
plt.figure(figsize=(20, 10))

### 그래프 제목 넣기
plt.title("기준월 및 기준일자별 버스 이용량 분석")

### 히트맵 그리기 : 히트맵은 seaborn 라이브러리에 존재한다
# - annot : False는 집계값 숨기기, True는 집계값 보이기
# - fmt : ".0f"는 소숫점 0자리까지 보이기
# - cmap : colormap, 컬러 색상그룹
sns.heatmap(df_pivot, annot=True, fmt=".0f", cmap="rocket_r")

### 그래프 출력
plt.show()

 

  • 그래프 해석
     - 1월 ~ 3월까지의 이용량을 분석한 결과 1월에 가장 많은 이용량을 나타내고 있으며, 2월에서 3월로 가면서 이용량이 점진적으로 줄어들고 있는 것으로 확인된다.
     - 1월 이용량이 가장 많은 이유는 포항시의 특성상 외부에서 관광객의 유입에 따라, 버스를 이용하는 사람들이 많을 것으로 예상된다.
     - 이에 따라, 포항시 관광객에 대한 데이터를 수집하여 해당 년월에 대한 데이터를 비교 분석해볼 필요성이 있다.

🐳 기준일 및 기준시간별 버스 이용량 시각화 분석

df_pivot = df_bus_card_tot.pivot_table(index = "기준일",
                                       columns = "기준시간",
                                       values = "승객연령",
                                       aggfunc = "count")
# 결측치 처리
df_pivot = df_pivot.fillna(0)

# 히트맵 그리기
plt.figure(figsize=(20, 10))

plt.title("기준일 및 기준시간별 버스 이용량 분석")

sns.heatmap(df_pivot, annot=True, fmt=".0f", cmap="YlGnBu")

plt.show()

  • 그래프 해석
    - 버스 이용량에 대한 분석결과, 일반적으로 출/퇴근 시간에 많아야 할 버스 이용량이 포항시의 경우 오후 시간대에 이용량이 밀집되어있다.
    - 특히, 오후1시와 3시에 높은 이용량을 나타내고 있다.
    - 이는 출/퇴근 시간에 자가 차량을 이용하는 사람이 많을 수도 있다는 예상을 할 수 있으며,
    인구 분포가 노령인구가 많기에 오후에 이용자가 많을 수도 있을 수 있다.
    - 따라서, 포항시 인구현황 데이터, 경제활동인구 분석을 통해 비교 분석이 가능할 것으로 예상된다.
    - 또한, 해당 이용량이 높은 시간대의 노선을 확인하여 특성 확인도 필요할 것으로 예상된다.

🐳 기준시간 및 분별 버스 이용량 시각화 분석

df_pivot = df_bus_card_tot.pivot_table(index = "기준시간",
                                       columns = "기준시간(분)",
                                       values = "승객연령",
                                       aggfunc = "count")
# 결측치 처리
df_pivot = df_pivot.fillna(0)

# 히트맵 그리기
plt.figure(figsize=(20, 10))

plt.title("기준시간 및 기준시간(분)별 버스 이용량 분석")

sns.heatmap(df_pivot, annot=True, fmt=".0f", cmap="coolwarm")

plt.show()

  • 그래프 해석
    - 출근 시간대의 버스이용량을  볼 때 오전 7시 55분 ~ 8시 10분 사이에 이용량이 많은 것으로 보인다.
    - 퇴근 시간대의 버스 이용량을 볼 때 오후 6시 ~ 6시 20분까지 이용량이 많은 것으로 보인다.
    - 특히 오후 3시 20분까지 버스 이용량이 매우 크게 나타나고 있다.
    - 오후 시간대 이용자에 대한 추가 확인이 필요할 것으로 보인다. 

🐳 기준일 및 시간별 버스내체류시간(분) 시각화 분석

df_pivot = df_bus_card_tot.pivot_table(index = "기준일",
                                       columns = "기준시간",
                                       values = "버스내체류시간(분)",
                                       aggfunc = "mean")
# 결측치 처리
df_pivot = df_pivot.fillna(0)

# 히트맵 그리기
plt.figure(figsize=(20, 10))

plt.title("기준일 및 시간별 버스내체류시간(분) 시각화 분석")

sns.heatmap(df_pivot, annot=True, fmt=".3f", cmap="BuPu")

plt.show()

  • 그래프 해석
     - 매월 1일에 장거리 이용자가 다소 분포하고 있으며 오전 5시부터 8시를 전후로 장거리 이용자가 증가하고 있다.
     - 오후 5시에 장거리 이용자가 매우 많게 나타난다. 이는 포항시 주변 상권(경제활동인구)의 출/퇴근 시간의 영향을 받을 수도 있을 것으로 예상된다.
     - 오후 7시 이후로는 장거리 이용자가 보편적으로 나타나고 있으며, 위에서 분석한 기준일 및 시간별 이용량 분석에서 확인한 바와 같이 7시 이후의 버스 이용량도 급격하게 줄어드는 것을 보아 저녁시간 버스 이용이 현저히 낮은것으로 보인다.
     - 장거리 이용자가 많은 시간대에 급행버스의 도입에 대한 추가 확인은 필요할 것으로 여겨진다.


막대그래프 시각화

🐳시간대 및 승객구별 버스내체류시간(분) 시각화

  • 필요한 데이터 추출
    - 기준시간, 승객연령, 버스내체류시간(분)
df_temp = pd.DataFrame()
df_temp["기준시간"] = df_bus_card_tot["기준시간"]
df_temp["승객구분"] = df_bus_card_tot["승객연령"]
df_temp["버스내체류시간"] = df_bus_card_tot["버스내체류시간(분)"]
df_temp


  • 승객 빈도 확인하기
df_temp["승객구분"].value_counts()


  • 그룹화 하기
  • 그룹화 후 head()
    - df_temp2.head(1)
    - groupby 이후의 head()의 역할 : 행단위로 조회하는게 아니라 그룹 단위로 조회한다.
    - groupby하고 head(1) 하면 각 그룹의 첫번째 값이 조회 된다.
# sum()
df_temp2 = df_temp.groupby(["기준시간", "승객구분"], as_index=False).sum()

# 내림차순 정렬
df_temp2 = df_temp2.sort_values(by=["버스내체류시간"], ascending=False)
df_temp2


  • 데이터의 행과 열을 교환하기
df_temp2.transpose()


  • 막대그래프 시각화 하기
fig = plt.figure(figsize=(25, 10))

plt.title("시간 및 승객구분별 버스내체류시간(분) 분석")

### hue : x축 및 y축을 기준으로 비교할 대상 컬럼 지정(범주형 데이터를 보통 사용)
sns.barplot(x="기준시간", y="버스내체류시간", hue="승객구분", data=df_temp2)

plt.show()

 

 

밀도그래프 시각화( histplot )

  • 2개의 그래프 조합

🐳시간대 및 승객구별 버스내체류시간(분) 시각화

plt.figure(figsize=(12, 4))
plt.title("시간 및 승객구분별 버스내체류시간(분) 분석")

sns.histplot(data = df_temp2,
            x = "기준시간",
             
            ### 사용할 막대의 최대 갯수
            bins = 30,
             
            ### 막대그래프에 밀도 선그리기            
            kde = True,
             
            ### 범주 데이터
            hue = "승객구분",
             
            ### 여러 범주를 하나의 막대에 표현하기
            multiple = "stack",
            
            ### 비율로 표시
            stat = "density",
             
            ### 막대 너비 : 0.6은 60% 축소한 너비 사이즈
            shrink = 0.6)

plt.show()

 

 

선그래프 시각화

🐳승차하정류장별 버스내체류시간(분) 상위 30건 시각화 분석

  • 구간(승차정류장 ~ 하차정류장) 까지의 버스내 체류시간을 이용하여 체류시간이 많은 구간을 확인하기
df_temp3 = pd.DataFrame()
df_temp3["버스내체류시간"] = df_bus_card_tot["버스내체류시간(분)"]
df_temp3["승하차정류장"] = df_bus_card_tot["승차정류장"] + "-->" + \
                         df_bus_card_tot["하차정류장"]
df_temp3


  • 구간별 그룹화 하기
### 구간(승차정류장 ~ 하차정류장)별 버스내체류시간(분) sum() 그룹화 하기
df_temp_gp = df_temp3.groupby(["승하차정류장"], as_index=False).sum()

### 내림차순 정렬
df_temp_gp = df_temp_gp.sort_values(by=["버스내체류시간"], ascending=False)

### 상위 30건 추출하기
df_temp_gp.head(30)


  • 선 그래프 그리기
plt.figure(figsize=(12, 4))
plt.title("승하차정류장별 버스내 체류시간 분석")

### 선그래프
plt.plot(df_temp3["승하차정류장"], df_temp3["버스내체류시간"])

### x축 및 y축 제목 넣기
plt.xlabel("승하창정류장")
plt.ylabel("버스내체류시간(분)")

### x축의 값의 기울기를 이용하여 조정하기
# - xticks() : x축을 컨트롤하는 함수
plt.xticks(rotation=90)

### 격자선 표시하기
plt.grid(True)

plt.show()

728x90
반응형

+ Recent posts