no image
mpl-toolkits.basemap을 활용한 세계 발전소 데이터를 활용한 발전원별, 용량별 현황시각화 | Matplotlib
이전에 folium을 활용하여 데이터를 시각화한 적이 있습니다. 하지만, folium을 활용하면 html 형태로 산출물이 나오기 때문에 다른 프로젝트(웹 등) 적용에는 용이하나, 보고서와 같이 A4에 쓰기에는 이미지 캡처를 해야 하고, 필요한 정보 이상이 들어오기 때문에 마음이 안 들수도 있을 것 같습니다. https://seanpark11.tistory.com/52?category=916800  [Folium] 파이썬을 활용한 지역별 월전력판매량 시각화하기한전은 매달 전력통계월보를 발표한다. 해당 자료에는 여러가지 내용들이 반영되어 있는데, 그중에서 지역별 월전력판매량에 대해 한번 시각화를 해보려고 했고, leaflet.js에 기반한 파이썬 라이seanpark11.tistory.com 이를 위해 찾아본..
2021.11.13
no image
Selenium을 활용한 지자체 선거 당선인 데이터 가져오기 | Web Scraping
우리나라는 선관위가 운영하는 선거통계시스템이라는 포털을 통해 역대선거의 통계에 대한 데이터를 제공한다. 최근에 지역별 지자체장들과 관련된 데이터가 필요한 일이 있어서 잠시 살펴봤다. 여기서 수작업으로 데이터를 모으기엔 다소 귀찮은 점이 있는데 아래와 같이 크게 두가지가 있다. 민선7기까지 선출된만큼 살펴봐야하는 횟수 자체가 적지 않다. 광역지자체장은 선거 횟수에 비례해 나오지만, 기초지자체의 경우 가장 최근인 민선7기의 경우 16개에 달해 경우의 수가 증가한다. 위 가정에 따라 계산을 해본다면, 7[광역지자체] + (15 * 6 + 16) [기초지자체] = 113회 정도 데이터를 조회해야 할 필요가 발생 http://info.nec.go.kr/ 아쉽게도 위 사이트는 동적웹페이지라 BeautifulSoup..
2021.10.16
no image
[알고리즘] 파이썬으로 병합정렬/합병정렬 (Merge Sort) 구현하기
병합정렬, 또는 합병정렬(이후에는 "병합정렬"로 통일)이라고 불리는 merge sort는 O(n * logn) 비교기반 정렬 알고리즘이다. 병합정렬의 기본적인 개념은 다음과 같다. 정렬되지 않은 리스트를 원소 1개의 부분리스트로 분할 부분리스트가 하나만 남을 때까지 반복 병합하면서 정렬된 부분리스트 생성 마지막 하나 남은 부분리스트가 바로 정렬된 리스트 퀵정렬이 빠르고 효율적인 편이라 많은 정렬 알고리즘에 사용한다 했지만, 간혹 특정상황(이미 정렬된 경우)에서는 매우 비효율적이라고 한다. 하지만, 병합정렬의 경우 이러한 특이한 경우가 없이 O(n * logn)의 속도를 보장한다. 병합정렬의 간단한 알고리즘 순서도는 그렇다. 리스트를 절반으로 나눈다. (Divide) 각 리스트를 재귀적으로 병합정렬을 적용..
2021.10.10
no image
파이썬으로 여러 페이지에 있는 정부 보도자료 크롤링하기_페이지네이션 | Web Scraping
이전에 파이썬을 활용해 정부 보도자료를 크롤링하는 게시글을 올린 적이 있다. 이 블로그내 제법 인기글 중 하나인데, 그만큼 많은 분들이 관심을 갖고 있다는 뜻이기도 할 것이다. 워낙 프로토타입 형태로 올렸던 글이라, 아주 간단한 형태의 크롤링만 올렸었다. 관련 링크는 아래에 첨부하였다. 2020.02.23 - [Python/Web Crawling] - 파이썬으로 정부 보도자료 크롤링 하기 (Web crawling) 파이썬으로 정부 보도자료 크롤링 하기 (Web crawling) 우리나라의 사회/산업/경제 전반적인 상황을 보기위해 신문도 참고할 자료 중 하나이긴 하지만, 각 정부 부처에서 제공하는 보도자료가 사실에 근접해서 보기에 훨씬 유용한 자료라 생각한다. seanpark11.tistory.com 지금..
2021.10.04
no image
[알고리즘] 파이썬으로 퀵정렬(Quick Sort) 구현하기
이전까지 기록했던 알고리즘 (선택정렬, 버블정렬, 삽입정렬)들은 시간 복잡도가 O(N**2)로 데이터의 개수가 증가하게 되면, 처리속도가 매우 느려지는 알고리즘들이었다. 하지만, 이번에 구현해보려는 퀵정렬의 경우, 평균적인 복잡도가 O(N*logN)으로 상대적으로 빠른 알고리즘이다. 퀵정렬의 정의는 다음과 같다. " 기준값(pivot)을 기준으로 작거나 같은 값을 지닌 데이터는 앞으로, 큰 값을 지닌 데이터는 뒤로 가도록 하여 분리하며 정렬하는 방법이다 " 정의에서 볼 수 있는 중요 포인트는 1) 기준값이 존재한다는 점, 2) 기준값을 기준으로 분리를 하는 분할정복 알고리즘이라는 것이다. 알고리즘을 이해하고, 상식선에서 같이 알고 넘어가자. # 퀵정렬: 특정한 값을 기준으로 큰 숫자와 작은 숫자를 서로 ..
2021.09.19
no image
파이썬을 활용한 지역별 월전력판매량 시각화하기 | Folium
한전은 매달 전력통계월보를 발표한다. 해당 자료에는 여러가지 내용들이 반영되어 있는데, 그중에서 지역별 월전력판매량에 대해 한번 시각화를 해보려고 했고, leaflet.js에 기반한 파이썬 라이브러리인 folium을 이용하면 편하겠다 싶어 한번 만들어봤다. 아직은 더 스터디가 필요한 부분이라 대부분 folium 정식문서에 있는 코드들을 가져다 썼는데도 잘 돌아간다. import jsonimport pandas as pdimport foliumfrom folium import pluginsfrom folium.plugins import HeatMap# folium 그리기month = '2020-04-30'column_list = ['Month', 'Seoul', 'Busan', 'Daegu', 'Inche..
2021.09.04
no image
[알고리즘] 파이썬으로 삽입 정렬(Insertion Sort) 구현하기
삽입정렬은 필요할 때만 적절한 위치에 삽입하는 알고리즘이다. 이것도 시간복잡도는 O(N**2)이나, 필요할 때만 수행하기 때문에 경우에 따라서는 굉장히 빨리 작동할 수도 있다. 따라서 앞서 설명했던 다른 알고리즘들(선택정렬, 버블정렬) 보다는 효율적이라고 할 수 있다. 삽입정렬의 순서는 다음과 같다고 정리할 수 있다. 1. 주어진 리스트에서 비교할 값을 선정한다 2. 비교할 값을 이전까지의 값들과 '필요한 경우'에만 비교한다 주어진 숫자 리스트를 오름차순으로 정렬하라는 문제에 대하여 위 순서에 따라 코드로 구현해보면 다음과 같다. # 필요할 때만, 적절한 위치에 삽입하여 정렬 def insertion_sort(array): for i in range(len(array)-1): j = i while (j>..
2021.09.04
no image
[알고리즘] 파이썬으로 버블 정렬 (Bubble Sort) 구현하기
버블정렬은 아이디어 자체는 매우 쉬운 알고리즘이다. 바로 옆에 있는 것과 비교해서 정렬하는 것이다. 아이디어가 쉬운 만큼 코드도 어렵지 않게 작성할 수 있지만, 효율성은 매우 낮다고 알려져 있어 앞으로 이런 코드를 쓸 일이 있을지는 잘 모르겠다. 그래도 알고리즘을 공부하는 입장에서 하나씩 접하면서 생각하는 습관이 필요할 것이다. 구현해보고자 하는 코드는 작은 값부터 큰 값까지 정렬하는 것으로, 우선 코드부터 살펴보면 다음과 같다. # 옆에 있는 값과 비교해 더 작은 값을 앞으로 보내기 # 하지만, 효율성이 가장 낮은 알고리즘 def bubble_sort(array): for i in range(len(array)): for j in range(len(array)-i-1): if (array[j] > ar..
2021.09.02
[알고리즘] 파이썬으로 선택정렬 (Selection Sort) 구현하기
정렬 알고리즘 중 하나인 선택정렬(selection sort)은 데이터들에 대해 가장 작은 데이터를 찾아 가장 앞의 데이터와 교환해나가는 알고리즘이다. 알고리즘에 대한 공부를 위해 적절한 강의를 찾았는데, 기본적으로 제공되는 내용은 C를 기반으로 하기 때문에 이를 Python으로 바꿔서 해보려고 한다. (모든 내용은 아래 참고의 블로그 및 유튜브를 참조) 선택정렬을 하기 위해서 실행하는 알고리즘의 순서는 다음과 같다. 1. 주어진 배열을 하나씩 탐색한다. 2. 탐색 지점부터 배열의 끝까지 값 중 가장 작은 값을 찾는다. 3. 그 작은 값을 탐색 지점에 놓고, 탐색 지점의 값을 가장 작은 값이 있던 곳에 놓는다. (데이터 교환, 스와핑) 그 전체 알고리즘은 아래와 같다. array = [1, 10, 5, ..
2021.08.29
반응형

이전에 folium을 활용하여 데이터를 시각화한 적이 있습니다. 하지만, folium을 활용하면 html 형태로 산출물이 나오기 때문에 다른 프로젝트(웹 등) 적용에는 용이하나, 보고서와 같이 A4에 쓰기에는 이미지 캡처를 해야 하고, 필요한 정보 이상이 들어오기 때문에 마음이 안 들수도 있을 것 같습니다.

 

https://seanpark11.tistory.com/52?category=916800 

 

[Folium] 파이썬을 활용한 지역별 월전력판매량 시각화하기

한전은 매달 전력통계월보를 발표한다. 해당 자료에는 여러가지 내용들이 반영되어 있는데, 그중에서 지역별 월전력판매량에 대해 한번 시각화를 해보려고 했고, leaflet.js에 기반한 파이썬 라이

seanpark11.tistory.com

 

이를 위해 찾아본 결과 matplotlib의 third-party 개념의 라이브러리(mpl-toolkits.basemap)가 있는데, 이를 해결하기에 꽤 괜찮아보여 시도해봤고 개인적으론 만족스러운 결과를 얻을 수 있어 공유하고자 합니다. 즉, 1) 좌표 정보를 갖고 있고, 2) 자신이 원하는 정보만 지도에 얹고 싶은 경우에 아래와 같은 사진의 결과를 얻을 수 있습니다.

 

이번 글의 목적은 다음과 같습니다.

  • 위 사진과 같은 지도이미지에 전세계에 위치한 발전소의 발전원별(색), 용량별(크기)로 분포도를 scatter plot
  • 여기서 전세계 위치는 위도, 경도로 확인 가능하여야 함

 

우선 간단히 코딩을 하기 위해 필요한 환경을 공유합니다. 여기서 특징은 이 라이브러리가 더이상 pip를 지원하지 않고 업데이트를 하지 않아 conda 환경에서 설치하고 사용해야 한다는 점입니다. 그러다보니, 설치가 쉽지 않을 수 있는데 그냥 아래처럼 버전을 맞추고, 가상환경을 설정해 진행하는 것을 추천합니다.

Version

Python = 3.8.5
pandas = 1.1.3
numpy = 1.21.3
matplotlib = 3.4.3
basemap = 1.2.2

보통 conda 환경에서 설치할 때는 "conda install [package]" 형태로 하게 되는데, 여기서는 아래 두가지로 설치하여야 하는 것으로 보입니다. 

conda install -c conda-forge basemap
conda install -c conda-forge proj

혹시 KeyError: 'PROJ_LIB'가 나온다면, 아래 접은 글을 확인해 주시기 바랍니다. (물론 밑에도 관련 코드 작성)

 

이렇게 설치가 완료되면, 데이터셋을 적재하고 코드를 실행하겠습니다. 데이터셋은 World Resources Institute에 Global Power Plant Database 를 csv로 제공하고 있습니다. 확보한 데이터셋을 파이썬 환경에 올리고, 코드에 필요한 라이브러리들을 불러오겠습니다.

 

여기서 scickit learn의 label encoder를 활용했는데, 이는 발전원 데이터(텍스트)를 색으로 구분하고, 이를 위해 필요한  인코딩을 수행했습니다.

# 1. Load the data
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from sklearn.preprocessing import LabelEncoder

global_power_df = pd.read_csv('global_power_plant_database.csv')

# Encooding with fuel types
le = LabelEncoder()
global_power_df['primary_fuel_encoded'] = le.fit_transform(global_power_df['primary_fuel'])

 

다음은 지도에 필요한 배경을 그리겠습니다. 아래 코드대로 작성하면, 처음에 봤던 세계전도같은 것이 global_m에 담기게 됩니다. 여기서 사이즈는 (160, 120)으로 설정하였는데, 적절한 크기로 설정하지 않으면 scatter plot의 버블이 지도에 비해 너무 커질 수도 있으니 주의하셔야 합니다..

 

# 2. Draw map background

fig = plt.figure(figsize=(160,120))
global_m = Basemap(projection="cyl", 
                   resolution=None, 
                   llcrnrlat=-90, 
                   urcrnrlat=90, 
                   llcrnrlon=-180, 
                   urcrnrlon=180)
global_m.shadedrelief()

 

이제 마지막으로 scatter plot으로 버블차트를 그립니다. Basemap이 기본적으로 matplotlib의 third-party이기 때문에 plt로 그려줄 수 있고, 아래처럼 코드로 구현이 가능합니다. 여기서 위에서 인코딩한 발전원별 데이터를 활용해 줍니다.

 

# 3. Scatter power location
global_m.scatter(global_power_df['longitude'], global_power_df['latitude'], latlon=True,
                c=global_power_df['primary_fuel_encoded'], s=global_power_df['capacity_mw'], 
                alpha=0.5)
cb = plt.colorbar(label ='primary_fuel', location='bottom')
cb.set_ticks([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14])
cb.set_ticklabels(['Biomass', 'Coal', 'CoGen', 'Gas', 'Geothermal', 'Hydro', 'Nuclear', 
                   'Oil', 'Other', 'Petcoke', 'Solar', 'Storage', 'Waste', 'Ocean', 'Wind'])
plt.show()

그리고 아래와 같은 결과를 얻었습니다. 다만, ticklabels의 경우 위 사이즈대로 하면 저렇게 크게 글자가 나오지 않아, 이 대신 잇몸이라고 선택한 방법은 한번은 사이즈를 크게, 다른 한번은 작게 해서 나온 두 이미지를 편집했습니다.

 

개인적으론 시각화 자체는 만족스러웠으나, 색을 colorbar로 활용하다보니, 인근에 있는 색과는 크게 구분되지 않는다는 단점이 있습니다. 이 부분은 나중에 버블 색을 지정해서 하는 방향으로 수정해서 시도해서 조금 더 눈에 잘 띄도록 할 필요가 있어 보입니다.

 


참고자료:

https://matplotlib.org/basemap/api/basemap_api.html

 

matplotlib basemap toolkit — Basemap Matplotlib Toolkit 1.2.1 documentation

Interpolate data (datain) on a rectilinear grid (with x = xin y = yin) to a grid with x = xout, y= yout. Note If datain is a masked array and order=1 (bilinear interpolation) is used, elements of dataout will be masked if any of the four surrounding points

matplotlib.org

https://wscode.tistory.com/9

 

[Python/Basemap]기상관측망 시각화

개발자 D 주제 : Basemap를 활용한 기상관측망 시각화 작업 데이터 : 종관기상관측(ASOS), 방재기상관측(AWS) 기상자료개방포털 ▶ 데이터 ▶ 메타데이터 ▶ 관측지점정보           (data.kma.go.kr/tme

wscode.tistory.com

https://jakevdp.github.io/PythonDataScienceHandbook/04.13-geographic-data-with-basemap.html

 

Geographic Data with Basemap | Python Data Science Handbook

Map Projections¶ The first thing to decide when using maps is what projection to use. You're probably familiar with the fact that it is impossible to project a spherical map, such as that of the Earth, onto a flat surface without somehow distorting it or

jakevdp.github.io

https://datasets.wri.org/dataset/globalpowerplantdatabase

 

Global Power Plant Database - Data | World Resources Institute

The Global Power Plant Database is a comprehensive, open source database of power plants around the world. It centralizes power plant data to make it easier to navigate, compare and draw insights...

datasets.wri.org

 

반응형
반응형

우리나라는 선관위가 운영하는 선거통계시스템이라는 포털을 통해 역대선거의 통계에 대한 데이터를 제공한다. 최근에 지역별 지자체장들과 관련된 데이터가 필요한 일이 있어서 잠시 살펴봤다. 여기서 수작업으로 데이터를 모으기엔 다소 귀찮은 점이 있는데 아래와 같이 크게 두가지가 있다.

  • 민선7기까지 선출된만큼 살펴봐야하는 횟수 자체가 적지 않다.
  • 광역지자체장은 선거 횟수에 비례해 나오지만, 기초지자체의 경우 가장 최근인 민선7기의 경우 16개에 달해 경우의 수가 증가한다.
  • 위 가정에 따라 계산을 해본다면, 7[광역지자체] + (15 * 6 + 16) [기초지자체] = 113회 정도 데이터를 조회해야 할 필요가 발생

 

http://info.nec.go.kr/

 

아쉽게도 위 사이트는 동적웹페이지라 BeautifulSoup으로 크롤링하기엔 한계가 있다. 이런 경우 (조금은 느리지만) 대안은 Selenium과 브라우저 드라이버(여기선 Chrome)를 활용한 방법으로 활용하는 것이 적절하다. 주의할 점은 Chrome은 매번 (자동으로) 업데이트가 되는만큼, 현재 크롬의 버전과 다른 버전의 드라이버를 사용하는 경우 동작하지 않을 위험이 있다.

 

관련링크:

- Selenium Docs (https://selenium-python.readthedocs.io/)

- Chrome Driver (https://chromedriver.chromium.org/downloads)

코드의 순서는 다음과 같이 동작하도록 구현하였다.

  1. 크롬을 통해 선거통계시스템에 접속
  2. 클릭을 통해 지방선거 당선인 명부에 이동 ("역대선거" > "당선인" > "당선인명부" > "지방선거" 탭을 클릭해 이동)
  3. 콤보박스의 내용을 가져와 리스트로 가져옴
  4. 리스트내 아이템 중 하나를 선택해 "검색" 버튼 클릭
  5. 조회되는 사이트의 표 내용을 가져와 Dataframe에 추가하기
  6. 콤보박스 아이템 리스트의 다음으로 iterate (4-5 반복)
  7. (번외) 한자가 인코딩이 안되는 문제를 해결하기 위해 한문이름 날리기
Version
Python = 3.8
Pandas = 1.3.1
BeautifulSoup = 4.9.3
Selenium = 3.141.0

(참고로 Selenium은 최근에 새로운 버전이 나와 최신버전으로 구현고자 할 경우 아래 코드가 동작하지 않습니다)

 

위 순서를 구현한 코드는 다음과 같다. 읽어보고 이해가 필요하다면, 각 순서별로 쪼개 살펴본 아래를 살펴보길.

import pandas as pd
from selenium import webdriver
from bs4 import BeautifulSoup
import time

def get_data(n_th, city):
    html = driver.page_source
    soup = BeautifulSoup(html, 'html.parser')
    col_name = [col.get_text() for col in soup.find_all('th')]
    data = [d.get_text().strip() for d in soup.find_all('td')]
    df = pd.DataFrame(columns=col_name)
    row_number = int(len(soup.find_all('td')) / len(col_name))
    for i in range(row_number):
        start = i * len(col_name)
        df.loc[len(df)] = data[start:start + len(col_name)]
    df['n_th'] = n_th
    df['city'] = city
    return df

driver = webdriver.Chrome('./venv/chromedriver.exe')
driver.get('http://info.nec.go.kr/')
driver.switch_to.default_content()
driver.switch_to.frame('main')

driver.find_element_by_class_name('eright').click()
driver.implicitly_wait(5)
driver.find_element_by_xpath('//*[@id="presubmu"]/li[5]/a').click()
driver.implicitly_wait(5)
driver.find_element_by_xpath('//*[@id="header"]/div[4]/ul/li[1]/a').click()
driver.implicitly_wait(5)
driver.find_element_by_xpath('//*[@id="electionType4"]').click()
driver.implicitly_wait(5)

df_election = pd.DataFrame()
election_name = driver.find_element_by_xpath('//*[@id="electionName"]')
n_th_election = [option.text for option in election_name.find_elements_by_tag_name("option")]
n_th_election = n_th_election[1:]
for n_th in n_th_election:
    election_name = driver.find_element_by_xpath('//*[@id="electionName"]')
    election_name.send_keys(n_th)
    time.sleep(3)
    election_code = driver.find_element_by_xpath('//*[@id="electionCode"]')
    election_code_lst = [option.text for option in election_code.find_elements_by_tag_name("option")]
    election_code_lst = election_code_lst[1:3]
    for code in election_code_lst:
        election_code = driver.find_element_by_xpath('//*[@id="electionCode"]')
        election_code.send_keys(code)
        time.sleep(3)
        if code == election_code_lst[-1]:
            # 시군구의 장의 경우만
            city_code = driver.find_element_by_xpath('//*[@id="cityCode"]')
            city_code_lst = [option.text for option in city_code.find_elements_by_tag_name("option")]
            city_code_lst = city_code_lst[1:]
            for city in city_code_lst:
                city_code = driver.find_element_by_xpath('//*[@id="cityCode"]')
                city_code.send_keys(city)
                driver.find_element_by_xpath('//*[@id="searchBtn"]').click()
                df_election = pd.concat([df_election, get_data(n_th, city)], ignore_index=True)
                time.sleep(3)
        else:
            driver.find_element_by_xpath('//*[@id="searchBtn"]').click()
            df_election = pd.concat([df_election, get_data(n_th, None)], ignore_index=True)
            time.sleep(3)

    time.sleep(3)

for ind in range(len(df_election)):
    itm = df_election.loc[ind, '성명(한자)']
    itm = itm[:itm.find('(')]
    df_election.loc[ind, '성명(한자)'] = itm

1.크롬을 통해 선거통계시스템에 접속

2.클릭을 통해 지방선거 당선인 명부에 이동

("역대선거" > "당선인" > "당선인명부" > "지방선거" 탭을 클릭해 이동)

우선 크롬드라이버를 통해 통계시스템에 접속하는 과정이다. 크게 설명이 필요한 부분은 아니니 스킵

from selenium import webdriver
import time
# 크롬 드라이버를 통해 통계시스템 접속
driver = webdriver.Chrome('./venv/chromedriver.exe')
driver.get('http://info.nec.go.kr/')
driver.switch_to.default_content()
driver.switch_to.frame('main')
# 지방선거 페이지로 이동
driver.find_element_by_class_name('eright').click()
driver.implicitly_wait(5)
driver.find_element_by_xpath('//*[@id="presubmu"]/li[5]/a').click()
driver.implicitly_wait(5)
driver.find_element_by_xpath('//*[@id="header"]/div[4]/ul/li[1]/a').click()
driver.implicitly_wait(5)
driver.find_element_by_xpath('//*[@id="electionType4"]').click()
driver.implicitly_wait(5)

3. 콤보박스의 내용을 가져와 리스트로 가져옴

선거 유형별로 선택하기 위해서는 화살표를 눌러 목록을 선택하는 방식인 콤보박스를 활용하고 있는데, 개발자도구를 통해 살펴보면 아래와 같이 나오는 것을 확인할 수 있다.

import pandas as pd
# 데이터를 넣을 빈 데이터프레임 생성 
df_election = pd.DataFrame()
# 콤보박스 목록내 아이템 리스트로 만들기
election_name = driver.find_element_by_xpath('//*[@id="electionName"]')
n_th_election = [option.text for option in election_name.find_elements_by_tag_name("option")]
n_th_election = n_th_election[1:]

election_code_lst = [option.text for option in election_code.find_elements_by_tag_name("option")]
election_code_lst = election_code_lst[1:3]

city_code_lst = [option.text for option in city_code.find_elements_by_tag_name("option")]
city_code_lst = city_code_lst[1:]

위에 코드는 선거회차, 선거유형(광역지자체, 기초지자체 등) 및 시도를 보관하기 위한 리스트를 만드는 코드이다. 다만, 내가 원하는 정보만 가져오기 위해 리스트 중 일부만 발췌해서 리스트를 재정의하였다.

(e.g. election_code_lst = ['시도지사선거', '구시군의장선거'])

4. 리스트내 아이템 중 하나를 선택해 "검색" 버튼 클릭

콤보박스는 하나를 선택할 때에는 해당 요소를 찾아 send_keys 메써드를 사용해주면 된다. 여기서 주의할 것이 광역지자체와 기초지자체 선거 페이지의 경우 "시도"에 관한 콤보박스가 있느냐 없느냐 문제가 있는데, 이건 if문으로 해결해주자. 즉, 위에서 만들어준 election_code_lst의 아이템(code) 중 끝에 있는 값을 선택했느냐 여부에 따라 "시도" 콤보박스 관련 코드를 실행여부를 결정하게끔 한다. 코드는 다음과 같이 할 수 있다. (위의 3단계도 같이 포함)

# 회차 선택
for n_th in n_th_election:
    election_name = driver.find_element_by_xpath('//*[@id="electionName"]')
    election_name.send_keys(n_th)
    time.sleep(3)
    election_code = driver.find_element_by_xpath('//*[@id="electionCode"]')
    election_code_lst = [option.text for option in election_code.find_elements_by_tag_name("option")]
    election_code_lst = election_code_lst[1:3]
    # 선거유형 선택
    for code in election_code_lst:
        election_code = driver.find_element_by_xpath('//*[@id="electionCode"]')
        election_code.send_keys(code)
        time.sleep(3)
        if code == election_code_lst[-1]:
            # 시군구의 장의 경우만, 시도 선택
            city_code = driver.find_element_by_xpath('//*[@id="cityCode"]')
            city_code_lst = [option.text for option in city_code.find_elements_by_tag_name("option")]
            city_code_lst = city_code_lst[1:]
            for city in city_code_lst:
                city_code = driver.find_element_by_xpath('//*[@id="cityCode"]')
                city_code.send_keys(city)
                driver.find_element_by_xpath('//*[@id="searchBtn"]').click() #검색버튼 클릭
                time.sleep(3)
        else:
            driver.find_element_by_xpath('//*[@id="searchBtn"]').click() #검색버튼 클릭
            time.sleep(3)

5. 조회되는 사이트의 표 내용을 가져와 Dataframe에 추가하기

일반적으로 BeautifulSoup은 동적웹페이지에서는 힘을 발휘하기 힘들지만, Selenium을 통해 소스를 가져오면 적용이 가능하다. 주어진 테이블의 데이터를 가져오기 위해 열이름을 위한 'th', 데이터를 위한 'td'를 찾아준다. 그렇게 해서 만든 Data frame을 반환하는 함수를 get_data(n_th, city)로 해서 작성해보았다.

from bs4 import BeautifulSoup
def get_data(n_th, city):
    html = driver.page_source
    soup = BeautifulSoup(html, 'html.parser')
    col_name = [col.get_text() for col in soup.find_all('th')]
    data = [d.get_text().strip() for d in soup.find_all('td')]
    df = pd.DataFrame(columns=col_name)
    row_number = int(len(soup.find_all('td')) / len(col_name))
    for i in range(row_number):
        start = i * len(col_name)
        df.loc[len(df)] = data[start:start + len(col_name)]
    df['n_th'] = n_th
    df['city'] = city
    return df

그리고 이렇게 작성한 get_data함수를 기존 데이터 프레임에 추가할 수 있도록 조치해주자.

if code == election_code_lst[-1]:
    df_election = pd.concat([df_election, get_data(n_th, city)], ignore_index=True)
else:
    df_election = pd.concat([df_election, get_data(n_th, None)], ignore_index=True)

6. 콤보박스 아이템 리스트의 다음으로 iterate (4-5 반복)

Iterate은 For문으로 반복하면 되는거고, 위 내용들을 총 종합해서 만들어주면 된다. 코드는 위를 참고

7. (번외) 한자가 인코딩이 안되는 문제를 해결하기 위해 한문이름 날리기

이렇게 가져온 데이터를 활용하기 위해 파이썬 자체에서 처리할 수도 있지만, 엑셀 등으로 옮겨서 처리하고 싶은 경우도 있을 것이다. 이 경우 인코딩 문제가 발생하는데, 성명부분에 한자이름에서 인식을 못하는 문제가 발생한다. 따라서, 이 문제를 해결하기 위해 한자명을 제거하는 코드를 작성하였다.

사실, 한자코드를 직접 읽어서 처리하기 보다는 한자명이 ( )안에 있다는 점에서 착안, '('을 기준으로 데이터클렌징을 해준게 전부인데, 여기서는 잘 작동한다.

for ind in range(len(df_election)):
    itm = df_election.loc[ind, '성명(한자)']
    itm = itm[:itm.find('(')]
    df_election.loc[ind, '성명(한자)'] = itm

사실 데이터가 당장 필요한 상황에서 수작업이 귀찮아서 짠 코드다보니, 예외처리를 넣지 않았거나, 최악의 경우 for문이 3번까지 중첩이 가능하는 등 코드의 효율성을 보자면,,,, 잘 모르겠다.

반응형
반응형

 

병합정렬, 또는 합병정렬(이후에는 "병합정렬"로 통일)이라고 불리는 merge sort는 O(n * logn) 비교기반 정렬 알고리즘이다. 병합정렬의 기본적인 개념은 다음과 같다.

 

  • 정렬되지 않은 리스트를 원소 1개의 부분리스트로 분할
  • 부분리스트가 하나만 남을 때까지 반복 병합하면서 정렬된 부분리스트 생성
  • 마지막 하나 남은 부분리스트가 바로 정렬된 리스트

퀵정렬이 빠르고 효율적인 편이라 많은 정렬 알고리즘에 사용한다 했지만, 간혹 특정상황(이미 정렬된 경우)에서는 매우 비효율적이라고 한다. 하지만, 병합정렬의 경우 이러한 특이한 경우가 없이 O(n * logn)의 속도를 보장한다. 병합정렬의 간단한 알고리즘 순서도는 그렇다.

 

  1. 리스트를 절반으로 나눈다. (Divide)
  2. 각 리스트를 재귀적으로 병합정렬을 적용해 정렬 (Conquer)
  3. 두 리스트를 병합한다. (Combine, Merge)

위 알고리즘에 따라, 참고내용들을 종합해 작성해본 코드는 다음과 같다.

 

def merge_sort(a):
    def sort(unsorted_list):
        if len(unsorted_list) <= 1:
            return
        # 두개의 리스트로 분할. 아래에서 재귀적으로 시행
        mid = len(unsorted_list) // 2
        left = unsorted_list[:mid]
        right = unsorted_list[mid:]
        merge_sort(left)
        merge_sort(right)
        merge(left, right)
        
    def merge(left, right):
        i = 0
        j = 0
        k = 0
        while (i < len(left)) and (j < len(right)):
            if left[i] < right[j]:
                a[k] = left[i]
                i += 1
                k+= 1
            else:
                a[k] = right[j]
                j += 1
                k+= 1
        # 남은 데이터 삽입
        while i < len(left):
            a[k] = left[i]
            i += 1
            k+= 1
        while j < len(right):
            a[k] = right[j]
            j += 1
            k+= 1
        print(a)
    sort(a)

 

위 코드를 적용하기 위해 아래처럼 배열을 만들고, 적용해보면 사진처럼 잘 나오는 것을 확인할 수 있다,

 

array = [1, 10, 5, 8, 7, 6, 4, 3, 2, 9]
merge_sort(array)

 


 

참고자료:

- https://ko.wikipedia.org/wiki/%ED%95%A9%EB%B3%91_%EC%A0%95%EB%A0%AC

 

합병 정렬 - 위키백과, 우리 모두의 백과사전

합병 정렬 또는 병합 정렬(merge sort)은 O(n log n) 비교 기반 정렬 알고리즘이다. 일반적인 방법으로 구현했을 때 이 정렬은 안정 정렬에 속하며, 분할 정복 알고리즘의 하나이다. 존 폰 노이만이 1945

ko.wikipedia.org

- https://m.blog.naver.com/ndb796/221227934987

 

7. 병합 정렬(Merge Sort)

지난 시간까지 시간 복잡도 O(N ^ 2)인 선택 정렬, 버블 정렬, 삽입 정렬 알고리즘을 공부했습니다. 이어...

blog.naver.com

-

반응형
반응형

이전에 파이썬을 활용해 정부 보도자료를 크롤링하는 게시글을 올린 적이 있다. 이 블로그내 제법 인기글 중 하나인데, 그만큼 많은 분들이 관심을 갖고 있다는 뜻이기도 할 것이다. 워낙 프로토타입 형태로 올렸던 글이라, 아주 간단한 형태의 크롤링만 올렸었다. 관련 링크는 아래에 첨부하였다.

 

2020.02.23 - [Python/Web Crawling] - 파이썬으로 정부 보도자료 크롤링 하기 (Web crawling)

 

파이썬으로 정부 보도자료 크롤링 하기 (Web crawling)

우리나라의 사회/산업/경제 전반적인 상황을 보기위해 신문도 참고할 자료 중 하나이긴 하지만, 각 정부 부처에서 제공하는 보도자료가 사실에 근접해서 보기에 훨씬 유용한 자료라 생각한다.

seanpark11.tistory.com

 

 

지금 돌이켜봤을 때, 위글에서 아쉬웠던 점이 몇 가지가 있는데 아래처럼 요약할 수 있을 것이다. 

 

1. requests란 훌륭한 라이브러리를 놔두고, urllib3를 사용했다는 점 (urllib이 파이썬 내장 라이브러리긴 하지만, 공식 홈페이지에서도 requests를 사용할 것을 장려하는 점을 고려하면 새로운 내용으로 바꾸는 것이 적절할 것)

2. 한번에 한 페이지의 내용만 가져오는 것을 이야기함

3. 동적으로 움직이는 반응형 웹사이트는 위 테크닉만으로는 어려운데, 새로운 기술에 대한 소개 필요

 

이중에서 이번 글을 통해 두가지를 해결할 수 있을 것 같은데, 그중 하나인 1번은 앞으로 크롤링하는데 자연스럽게 사용하면서 녹여낼 것이고 나머지 하나인 2번은 오늘 이야기할 페이지네이션을 통해 해결하고자 한다. 나머지 세번째는 셀레니움(Selenium)으로 일반적으로 해결할텐데, 이는 나중 글에서 논하고자 한다.

 

우선, 페이지네이션에 대해 살펴봐야 할텐데, 검색창을 통해 알아보면 페이지네이션은 웹개발에서 DB의 많은 정보를 한꺼번에 표현하기 어려우므로 페이지의 형태로 표현하는 것을 지칭한다. (게시판 등에서 쉽게 볼 수 있는 형태라 어렵진 않다) 이건 개발, 그중에서도 백엔드 단에서 하는 이야기이고, 우리가 관심있는 크롤링에서는 어떻다고 이야기할 수 있을까? 정확한 정의는 아직 찾지는 못했지만, 페이지 형태로 구현된 정보들을 페이지(일련번호)를 부여하면서 읽어온다고 이야기할 수 있을 것이다.


이전에 봤던 기획재정부 보도자료 홈페이지를 들어가보자. 가장 아래로 가보면 "1 2 3 4 5" 상자로 이뤄진 페이지로 갈 수 있는 박스가 있는 것을 확인할 수 있다. 이 상자들을 눌러보자. 그리고 그 URL을 한번 확인해보자.

https://www.moef.go.kr/nw/nes/nesdta.do?searchBbsId1=MOSFBBS_000000000028&menuNo=4010100&pageIndex=1

https://www.moef.go.kr/nw/nes/nesdta.do?searchBbsId1=MOSFBBS_000000000028&menuNo=4010100&pageIndex=2

https://www.moef.go.kr/nw/nes/nesdta.do?searchBbsId1=MOSFBBS_000000000028&menuNo=4010100&pageIndex=3

....

https://www.moef.go.kr/nw/nes/nesdta.do?searchBbsId1=MOSFBBS_000000000028&menuNo=4010100&pageIndex=1789

복사해보면 위처럼 나오게 되는데, 잘살펴보면 규칙이 하나 있다. ~do? 이후 &로 연결된 여러 파라미터들 중 pageIndex라는 것이 1,2,3,...으로 쭉 이어지는 것을 확인할 수 있다. (참고로, 글을 쓰는 현시점에 보도자료 맨 마지막페이지는 1789인데, 그게 맨 마지막에 있는 링크임을 확인할 수 있다.) 따라서, 해당 pageIndex에 대한 변화를 주는 것만으로도 해당 페이지에 대한 요청을 보낼 수 있다. 그렇다면, 이전에 사용했던 코드를 먼저 가져와서 참고하자.

 

import requests
from bs4 import BeautifulSoup

url = "http://www.moef.go.kr/nw/nes/nesdta.do?bbsId=MOSFBBS_000000000028&menuNo=4010100&pageIndex=1"
req = requests.get(url)
soup = BeautifulSoup(req, "html.parser")
soup.find_all('h3')

tag가 'h3'인 것은 이전 글에서 간단히 찾아봤으니 (물론 그때는 태그를 이용한 find_all을 사용하기 보다는 selector를 활용했지만), find_all을 통해 태그를 찾아준다면 위와 같이 리스트를 반환한다. 여기엔 태그가 여기저기 붙어있으니 좀 더 깔끔한 내용을 출력하기 위해서는 아래와 같이 적용해주면 된다.

for item in soup.find_all('h3'):
    print(item.text)


그럼 여기서 더 나아가서, 페이지별로 수집하기 위해서는 어떻게 하면될까? 앞서서도 설명했지만 pageIndex만 수정해주면 되는데, 여러가지 방법이 있지만 가장 간단한 방법으로 (5페이지만 가져오도록 하기 위한) 코드는 아래와 같이 작성하면 된다. 그리고 잘 나오는 것을 확인할 수 있다.

import requests
from bs4 import BeautifulSoup
# 원하는 태그의 내용을 담을 리스트(item_list)와 pageIndex를 비운 url 준비하기
item_list = []
url = "http://www.moef.go.kr/nw/nes/nesdta.do?bbsId=MOSFBBS_000000000028&menuNo=4010100&pageIndex="

# 보도자료 페이지별 웹사이트 요청해서 태그를 찾고, 그것을 리스트에 연장하기
for ind in range(1,6):
    req = requests.get(url+str(ind)).text
    soup = BeautifulSoup(req, "html.parser")
    item_list.extend(soup.find_all('h3'))

# 이쁘게 출력하기
for item in item_list:
    print(item.text)

 

Version
Python = v.3.8 (64 bit)
requests = v.2.26.0
Beautifulsoup = v.4.9.0
반응형
반응형

이전까지 기록했던 알고리즘 (선택정렬, 버블정렬, 삽입정렬)들은 시간 복잡도가 O(N**2)로 데이터의 개수가 증가하게 되면, 처리속도가 매우 느려지는 알고리즘들이었다. 하지만, 이번에 구현해보려는 퀵정렬의 경우, 평균적인 복잡도가 O(N*logN)으로 상대적으로 빠른 알고리즘이다. 

 

퀵정렬의 정의는 다음과 같다. 

" 기준값(pivot)을 기준으로 작거나 같은 값을 지닌 데이터는 앞으로, 큰 값을 지닌 데이터는 뒤로 가도록 하여 분리하며 정렬하는 방법이다 "

정의에서 볼 수 있는 중요 포인트는 1) 기준값이 존재한다는 점, 2) 기준값을 기준으로 분리를 하는 분할정복 알고리즘이라는 것이다. 알고리즘을 이해하고, 상식선에서 같이 알고 넘어가자. 

 

# 퀵정렬: 특정한 값을 기준으로 큰 숫자와 작은 숫자를 서로 교환한 뒤, 배열을 반으로 나눠 정렬
def partition(arr, start, end):
    pivot = arr[start]
    left = start + 1
    right = end
    done = False
     # left와 right가 엇갈릴 때까지 반복
    while not done:
        # pivot 보다 작은 값을 만날 때까지 반복
        while left <= right and arr[left] <= pivot:
            left += 1
        # pivot 보다 큰 값을 만날 때까지 반복
        while left <= right and pivot <= arr[right]:
            right -= 1
        if right < left:
            done = True
        else: # 엇갈린 상태면 피벗 값과 교체
            arr[left], arr[right] = arr[right], arr[left]
    arr[start], arr[right] = arr[right], arr[start]
    return right

def quick_sort(arr, start, end):
    if start < end:
    	print(arr)
        pivot = partition(arr, start, end)
        quick_sort(arr, start, pivot - 1) # pivot 보다 작은 왼쪽에 적용
        quick_sort(arr, pivot + 1, end) # pivot 보다 큰 오른쪽에 적용
    return arr

quick_sort(array, 0, len(array)-1)

원래 알고리즘에서 코드를 작성할 때는 '안경잡이 개발자' 블로그 및 유튜브를 참고하는데, 이번에는 위키피디아의 소스코드를 참고하였다. 코드 자체는 거의 비슷하고, 구현하는 결과도 거의 비슷했는데, 맨마지막 정렬이 안이뤄져 그에 대한 이유를 아직은 모르겠어서 위키피디아의 내용을 참고했다. 

 

(원래는 한 함수 내에 모든 것을 끝내려고 했는데, 위키처럼 나눠서 하는 게 나중에 코드에 대한 유지보수의 관점에서 더 쉬울 것이란 생각이다)

 

순서:

1. pivot 값을 설정한다

2. pivot을 기준으로 보다 큰 값을 만날 때까지(right), 그리고 작은 값을 만날 때까지(left) 비교한다

3. 위에서 찾은 right이 left보다 크면 (값이 엇갈리면) 교체한다

4. pivot 왼쪽과 오른쪽에 위 순서를 반복한다

 

 

동작하는 것을 보면 아래 사진과 같이 출력되는 것을 확인할 수 있다. (중복되서 출력되는데, 이건 quick_sort 함수 초반에 print를 하는데, 아래에서는 그 함수를 두번이나 적용하기 때문이다)

 

참고자료:

- 안경잡이 개발자, "5. 퀵 정렬(Quick Sort)", https://m.blog.naver.com/ndb796/221226813382

 

5. 퀵 정렬(Quick Sort)

지난 시간까지 다루었던 선택 정렬, 버블 정렬, 삽입 정렬 알고리즘은 모두 시간 복잡도 O(N^2)을 가지는...

blog.naver.com

- Wikipedia, "Quick Sort(퀵 정렬)", https://ko.wikipedia.org/wiki/%ED%80%B5_%EC%A0%95%EB%A0%AC

 

퀵 정렬 - 위키백과, 우리 모두의 백과사전

퀵 정렬(Quicksort)은 찰스 앤터니 리처드 호어가 개발한 정렬 알고리즘이다. 다른 원소와의 비교만으로 정렬을 수행하는 비교 정렬에 속한다. 퀵 정렬은 n개의 데이터를 정렬할 때, 최악의 경우에

ko.wikipedia.org

 

반응형
반응형

한전은 매달 전력통계월보를 발표한다. 해당 자료에는 여러가지 내용들이 반영되어 있는데, 그중에서 지역별 월전력판매량에 대해 한번 시각화를 해보려고 했고, leaflet.js에 기반한 파이썬 라이브러리인 folium을 이용하면 편하겠다 싶어 한번 만들어봤다.

 

아직은 더 스터디가 필요한 부분이라 대부분 folium 정식문서에 있는 코드들을 가져다 썼는데도 잘 돌아간다.

 

import json
import pandas as pd
import folium
from folium import plugins
from folium.plugins import HeatMap

# folium 그리기
month = '2020-04-30'
column_list = ['Month', 'Seoul', 'Busan', 'Daegu', 'Incheon',
               'Gwangju', 'Daejeon', 'Ulsan', 'Gyeong-gi',
               'Gangwon', 'Chungbuk', 'Chungnam', 'Jeonbuk',
               'Jeonnam', 'Kyungbuk', 'Kyungnam', 'Jeju',
               'Sejong', 'Hwangbuk', 'Total']
df = pd.read_csv('region.csv')
df.columns = column_list

# 광역지자체 SHP파일을 geojson 변환한 것을 읽어오기, 하단 참고내용 참조
state_geo = 'TL_SCCO_CTPRVN_Met.json'
json_data = open(state_geo, encoding='utf-8').read()
json_Result = json.loads(json_data)

dictionary = {'code': ['11', '26', '27', '28', '29', '30', '31', '41', '42',
                       '43', '44', '45', '46', '47', '48', '50', '36']}
region_data = pd.DataFrame(data=dictionary)
region_data['data'] = df.loc[df.Month == month, 'Seoul':'Sejong'].transpose().values

# 맵 그리기
bins = [0,  460017, 1228571, 1669235, 1975424, 2677516, 2853723, 3591552, 12000000]
m = folium.Map(location=[36, 127], zoom_start=7)
m.choropleth(
    geo_data=json_data,
    name='Electricity Use',
    data=region_data,
    columns=['code', 'data'],
    key_on='feature.properties.CTPRVN_CD',
    fill_color='BuPu',
    fill_opacity=0.7,
    line_opacity=0.3,
    color='gray',
    bins=bins
)
folium.LayerControl().add_to(m)
m.save(month+'.html')

 

마지막에 html로 다운로드 받은 것을 열어보면 아래 사진과 같이 잘 나오는 것을 확인할 수 있다. 다만, 바라는 것이 있다면 월별, 광역지자체별이 아닌 좀 더 작은단위(일별, 기초지자체별)로 쪼개서 줬으면 좋을 듯..

 


참고 및 도움받은 곳:

- GIS DEVELOPER, '대한민국 최신 행정구역(SHP) 다운로드', Retrieved from http://www.gisdeveloper.co.kr/?p=2332 

- Folium 정식 문서, Retrieved from http://python-visualization.github.io/folium/index.html

- dailyheumsi, '[지도 데이터 시각화] Part1. Geo Data와 Python', Retrieved from https://dailyheumsi.tistory.com/141?category=854906 

반응형
반응형

삽입정렬은 필요할 때만 적절한 위치에 삽입하는 알고리즘이다. 이것도 시간복잡도는 O(N**2)이나, 필요할 때만 수행하기 때문에 경우에 따라서는 굉장히 빨리 작동할 수도 있다. 따라서 앞서 설명했던 다른 알고리즘들(선택정렬, 버블정렬) 보다는 효율적이라고 할 수 있다. 

 

삽입정렬의 순서는 다음과 같다고 정리할 수 있다.

1. 주어진 리스트에서 비교할 값을 선정한다

2. 비교할 값을 이전까지의 값들과 '필요한 경우'에만 비교한다

 

주어진 숫자 리스트를 오름차순으로 정렬하라는 문제에 대하여 위 순서에 따라 코드로 구현해보면 다음과 같다.

# 필요할 때만, 적절한 위치에 삽입하여 정렬
def insertion_sort(array):
    for i in range(len(array)-1):
        j = i
        while (j>=0) and (array[j] > array[j+1]):
            temp = array[j]
            array[j] = array[j+1]
            array[j+1] = temp
            j -= 1
        print(array)
        
array = [1, 10, 5, 8, 7, 6, 4, 3, 2, 9]
insertion_sort(array)

이 알고리즘에서 생각해볼만한 문구는 '필요할 경우에만 비교'한다는 것이다. 일반적으로 어떤 조건에서만 돌아가게끔 하는 반복문은 while을 사용하면 구현이 가능하다는 점을 기억한다면, 앞서 다른 알고리즘에서 for문만 사용하다가 여기서 while문을 만나더라도 당황하지 않을 것이다.

 

(아래 링크는 파이썬 표준문서에서 이야기하는 while문에 대한 설명이니 필요한 사람만 참고)

 

https://docs.python.org/ko/3/tutorial/introduction.html#first-steps-towards-programming

 

다음은 위 코드를 구현했을 때, 정렬 작업의 출력 결과물이다. 상대적으로 이전의 알고리즘에 비해서는 적은 것을 확인할 수 있다.

 

참고 및 출처:

- 안경잡이 개발자, '4. 삽입정렬(Insertion Sort)' https://blog.naver.com/ndb796/221226806398

 

4. 삽입 정렬(Insertion Sort)

지난 시간까지 선택 정렬과 버블 정렬에 대해 알아보았습니다. 앞서 다룬 정렬 알고리즘 모두 시간 복잡도 ...

blog.naver.com

 

반응형
반응형

버블정렬은 아이디어 자체는 매우 쉬운 알고리즘이다. 바로 옆에 있는 것과 비교해서 정렬하는 것이다. 아이디어가 쉬운 만큼 코드도 어렵지 않게 작성할 수 있지만, 효율성은 매우 낮다고 알려져 있어 앞으로 이런 코드를 쓸 일이 있을지는 잘 모르겠다. 그래도 알고리즘을 공부하는 입장에서 하나씩 접하면서 생각하는 습관이 필요할 것이다.


구현해보고자 하는 코드는 작은 값부터 큰 값까지 정렬하는 것으로, 우선 코드부터 살펴보면 다음과 같다.

 

# 옆에 있는 값과 비교해 더 작은 값을 앞으로 보내기
# 하지만, 효율성이 가장 낮은 알고리즘
def bubble_sort(array):
    for i in range(len(array)):
        for j in range(len(array)-i-1):
            if (array[j] > array[j+1]):
                temp = array[j]
                array[j] =array[j+1]
                array[j+1] = temp
            print(array)
array = [1, 10, 5, 8, 7, 6, 4, 3, 2, 9]
bubble_sort(array)

 

선택정렬을 위한 알고리즘의 순서는 다음과 같다. 사실은 더 간단하게 나타낼 수도 있지만, 설명을 위해 비교적 길게 풀어쓴다.

 

1. 이전 비교 리스트에서 비교할 집단을 하나 줄여 선택한다. (첫번째 ~ 마지막 하나 전) 

2. 비교할 집단 내에서 원소를 순차적으로 선택한다

3. 선택한 원소가 옆의 원소와 비교해 앞의 값이 큰 경우, 데이터를 교환한다

3. 2~3를 반복하다가, 비교 집단의 원소를 모두 선택한 경우 1로 돌아간다

 

여기서 헛갈릴 수 있는 부분이 몇 가지가 보이는데, 우선 첫번째부터 마지막까지 원소가 가는 것이 아닌 마지막 하나 전까지만 가야한다는 점이다. 왜냐하면, 내가 선택한 원소(j)와 그 바로 다음 원소(j+1)를 비교할 것이기 때문이다.

 

다른 하나는 비교해야할 집단을 하나씩 줄여준다는 점이다. 이는 비교집단의 모든 원소를 비교하면, 집단의 맨 마지막에 가장 큰 원소가 위치하게 된다. (이는 직접 해보는 것만큼 이해가 빠른 것은 없는 듯하다) 생각해보면 앞서 위에서 얘기한 것과 비슷한 원리라고 느껴지기도 하는데, 이건 사람마다 다르게 생각할 수 있어 따로 설명은 하지 않으려고 한다.

 

아무튼, 위 코드를 구현해보면 아래와 같이 잘 정렬하는 것을 볼 수 있다.

 

 

출처 및 참고:

- 안경잡이개발자, "3. 버블 정렬(Bubble Sort)", https://blog.naver.com/ndb796/221226803544

 

3. 버블 정렬(Bubble Sort)

지난 시간에는 가장 작은 값을 선택해서 앞으로 보내는 선택 정렬(Selection Sort) 알고리즘에 대해 알아...

blog.naver.com

 

반응형
반응형

정렬 알고리즘 중 하나인 선택정렬(selection sort)은 데이터들에 대해 가장 작은 데이터를 찾아 가장 앞의 데이터와 교환해나가는 알고리즘이다. 알고리즘에 대한 공부를 위해 적절한 강의를 찾았는데, 기본적으로 제공되는 내용은 C를 기반으로 하기 때문에 이를 Python으로 바꿔서 해보려고 한다.

 

(모든 내용은 아래 참고의 블로그 및 유튜브를 참조)


선택정렬을 하기 위해서 실행하는 알고리즘의 순서는 다음과 같다.

 

1. 주어진 배열을 하나씩 탐색한다.

2. 탐색 지점부터 배열의 끝까지 값 중 가장 작은 값을 찾는다.

3. 그 작은 값을 탐색 지점에 놓고, 탐색 지점의 값을 가장 작은 값이 있던 곳에 놓는다. (데이터 교환, 스와핑)

 

그 전체 알고리즘은 아래와 같다.

array = [1, 10, 5, 8, 7, 6, 4, 3, 2, 9]

# 가장 작은 것을 선택해 제일 앞으로 보내기
def selection_sort(array):
    for i in range(len(array)):
        bound = 9999  # 비교를 위해서 데이터의 어떤 값보다 큰 숫자
        # 가장 작은 것을 찾고, 그 위치를 ind에 저장
        for j in range(i, len(array)):
            if (bound > array[j]):
                bound = array[j]
                ind = j
        # 스와핑 
        temp = array[i]
        array[i] = array[ind]
        array[ind] = temp
    print(array)

selection_sort(array)

 

결과는 잘 나오는 것으로 볼 수 있다. 다만, 이 알고리즘이 어떻게 동작하는지 보려면 함수 맨 마지막에 있는 print 함수를 들여쓰기해서 아래 코드 처럼 쓰면, 어떻게 동작하는지 잘 볼 수 있을 것이다. 

 

array = [1, 10, 5, 8, 7, 6, 4, 3, 2, 9]

# 가장 작은 것을 선택해 제일 앞으로 보내기
def selection_sort(array):
    for i in range(len(array)):
        bound = 9999  # 비교를 위해서 데이터의 어떤 값보다 큰 숫자
        # 가장 작은 것을 찾고, 그 위치를 ind에 저장
        for j in range(i, len(array)):
            if (bound > array[j]):
                bound = array[j]
                ind = j
        # 스와핑 
        temp = array[i]
        array[i] = array[ind]
        array[ind] = temp
    	print(array)

selection_sort(array)

 

참고:

- 안경잡이개발자, "2. 정렬의 개요와 선택정렬(Selection Sort)", https://blog.naver.com/ndb796/221226800661

 

2. 정렬의 개요와 선택 정렬(Selection Sort)

일반적으로 알고리즘을 공부할 때 가장 먼저 풀어보는 문제는 '정렬(Sort)' 문제입니다. 왜냐하면 정렬만...

blog.naver.com

 

반응형