티스토리 뷰

셀프 주유소는 정말 저렴할까 - 복사본

제 4장 셀프 주유소는 정말 저렴할까

구성 및 블로그 진행 과정

4-1 Selenium 사용하기
-------------------------------------------------------
4-2 서울시 구별 주유소 가격 정보 얻기
4-3 구별 주유 가격에 대한 데이터의 정리
4-4 셀프 주유소는 정말 저렴한지 boxplot으로 확인하기
-------------------------------------------------------
4-5 서울시 구별 주유 가격 확인
4-6 서울시 주유 가격 상.하위 10개 주유소 지도에 표기하기

출처: 파이썬으로 데이터 주무르기 by 민형기


In [1]:
# Selenium에서 webdriver를 import한다.
from selenium import webdriver

4-2 서울시 구별 주유소 가격 정보 얻기

4-1절에서 배운 Selenium의 지식만으로 https://goo.gl/VH1A5t 에 접속해서 서울시 구별 주유소 정보를 받아오자. 지역은 서울시 정보로 한정한다.

In [13]:
driver = webdriver.Chrome('chromedriver')
driver.get('http://www.naver.com')
In [14]:
driver.get('http://www.opinet.co.kr/searRgSelect.do')
driver.get('http://www.opinet.co.kr/searRgSelect.do')
In [15]:
Image.open('selenium4.png')
Out[15]:

위 사진에서 우리는 서울시만 검색할 것이니 가만히 두고, 종로구 라는 글자가 있는 부분은 바꿔줘야 한다. 리스트 박스 형태로 되어 있어서 해당 리스트의 내용을 받아와서 순차적으로 반환해주면 된다.

In [16]:
# 종로구라는 글자가 보이는 리스트 박스의 XPath를 이용하여 element를 찾고 gu_list_raw 변수에 저장한다.
gu_list_raw = driver.find_element_by_xpath('''//*[@id="SIGUNGU_NM0"]''')
In [17]:
Image.open('selenium5.png')
Out[17]:

구를 선택하는 리스트 박스의 태그의 select 옆에 세모모양을 눌러보면 option이라는 태그에 구 이름이 저장되어 있는 것을 알 수 있다.

In [18]:
# 구 리스트는 find_elements_by_tag_name으로 option이라는 태그를 찾으면 된다.
gu_list = gu_list_raw.find_elements_by_tag_name('option')
In [19]:
gu_names = [option.get_attribute('value') for option in gu_list]
gu_names.remove('')
gu_names
Out[19]:
['강남구',
 '강동구',
 '강북구',
 '강서구',
 '관악구',
 '광진구',
 '구로구',
 '금천구',
 '노원구',
 '도봉구',
 '동대문구',
 '동작구',
 '마포구',
 '서대문구',
 '서초구',
 '성동구',
 '성북구',
 '송파구',
 '양천구',
 '영등포구',
 '용산구',
 '은평구',
 '종로구',
 '중구',
 '중랑구']
In [20]:
#gu_names에서 첫번째 것을 한번 시험 삼아 입력해보자.
element = driver.find_element_by_id('SIGUNGU_NM0')
element.send_keys(gu_names[0])
In [21]:
Image.open('selenium6.png')
Out[21]:
In [23]:
#조회버튼의 Xpath를 찾아서 클릭
xpath ='''//*[@id="searRgSelect"]/span'''
element_sel_gu = driver.find_element_by_xpath(xpath).click()
In [24]:
Image.open('selenium7.png')
Out[24]:
In [25]:
#엑셀 저장 버튼을 눌러서 엑셀 내용으로 저장하자.
xpath = '''//*[@id="glopopd_excel"]/span'''
element_get_excel = driver.find_element_by_xpath(xpath).click()
In [26]:
import time
from tqdm import tqdm_notebook

for gu in tqdm_notebook(gu_names):
    element = driver.find_element_by_id('SIGUNGU_NM0')
    element.send_keys(gu)
    
    time.sleep(2)
    
    xpath ='''//*[@id="searRgSelect"]/span'''
    element_sel_gu = driver.find_element_by_xpath(xpath).click()
    
    time.sleep(1)
    
    xpath = '''//*[@id="glopopd_excel"]/span'''
    element_get_excel = driver.find_element_by_xpath(xpath).click()
    
    time.sleep(1)

In [28]:
Image.open('selenium8.png')
Out[28]:
In [27]:
# 드라이버를 닫고, 이제 저장된 엑셀 파일로 셀프 주유소가 실제로 저렴한지 여부를 확인해 보자.
driver.close()

4-3 구별 주유 가격에 대한 데이터의 정리

4-2절에서 받은 25개의 엑셀 파일을 우리가 다루는 data 폴더로 옮긴다. 이전처럼 read를 이용하여 읽으면 25줄을 입력해야지만 모두 읽을 수 있지만 파이썬의 glob 모듈을 사용하면 손쉽게 열 수 있다.

In [29]:
import pandas as pd
from glob import glob
In [30]:
# 지역*.xls는 지역으로 시작하면서 확장자가 xls인 파일들
glob('pydata/selenium/지역*.xls')
Out[30]:
['pydata/selenium\\지역_위치별(주유소) (1).xls',
 'pydata/selenium\\지역_위치별(주유소) (10).xls',
 'pydata/selenium\\지역_위치별(주유소) (11).xls',
 'pydata/selenium\\지역_위치별(주유소) (12).xls',
 'pydata/selenium\\지역_위치별(주유소) (13).xls',
 'pydata/selenium\\지역_위치별(주유소) (14).xls',
 'pydata/selenium\\지역_위치별(주유소) (15).xls',
 'pydata/selenium\\지역_위치별(주유소) (16).xls',
 'pydata/selenium\\지역_위치별(주유소) (17).xls',
 'pydata/selenium\\지역_위치별(주유소) (18).xls',
 'pydata/selenium\\지역_위치별(주유소) (19).xls',
 'pydata/selenium\\지역_위치별(주유소) (2).xls',
 'pydata/selenium\\지역_위치별(주유소) (20).xls',
 'pydata/selenium\\지역_위치별(주유소) (21).xls',
 'pydata/selenium\\지역_위치별(주유소) (22).xls',
 'pydata/selenium\\지역_위치별(주유소) (23).xls',
 'pydata/selenium\\지역_위치별(주유소) (24).xls',
 'pydata/selenium\\지역_위치별(주유소) (3).xls',
 'pydata/selenium\\지역_위치별(주유소) (4).xls',
 'pydata/selenium\\지역_위치별(주유소) (5).xls',
 'pydata/selenium\\지역_위치별(주유소) (6).xls',
 'pydata/selenium\\지역_위치별(주유소) (7).xls',
 'pydata/selenium\\지역_위치별(주유소) (8).xls',
 'pydata/selenium\\지역_위치별(주유소) (9).xls',
 'pydata/selenium\\지역_위치별(주유소).xls']
In [31]:
# station_files 변수에 각 엑셀 파일의 경로와 이름을 리스트로 저장
stations_files = glob('pydata/selenium/지역*.xls')
stations_files
Out[31]:
['pydata/selenium\\지역_위치별(주유소) (1).xls',
 'pydata/selenium\\지역_위치별(주유소) (10).xls',
 'pydata/selenium\\지역_위치별(주유소) (11).xls',
 'pydata/selenium\\지역_위치별(주유소) (12).xls',
 'pydata/selenium\\지역_위치별(주유소) (13).xls',
 'pydata/selenium\\지역_위치별(주유소) (14).xls',
 'pydata/selenium\\지역_위치별(주유소) (15).xls',
 'pydata/selenium\\지역_위치별(주유소) (16).xls',
 'pydata/selenium\\지역_위치별(주유소) (17).xls',
 'pydata/selenium\\지역_위치별(주유소) (18).xls',
 'pydata/selenium\\지역_위치별(주유소) (19).xls',
 'pydata/selenium\\지역_위치별(주유소) (2).xls',
 'pydata/selenium\\지역_위치별(주유소) (20).xls',
 'pydata/selenium\\지역_위치별(주유소) (21).xls',
 'pydata/selenium\\지역_위치별(주유소) (22).xls',
 'pydata/selenium\\지역_위치별(주유소) (23).xls',
 'pydata/selenium\\지역_위치별(주유소) (24).xls',
 'pydata/selenium\\지역_위치별(주유소) (3).xls',
 'pydata/selenium\\지역_위치별(주유소) (4).xls',
 'pydata/selenium\\지역_위치별(주유소) (5).xls',
 'pydata/selenium\\지역_위치별(주유소) (6).xls',
 'pydata/selenium\\지역_위치별(주유소) (7).xls',
 'pydata/selenium\\지역_위치별(주유소) (8).xls',
 'pydata/selenium\\지역_위치별(주유소) (9).xls',
 'pydata/selenium\\지역_위치별(주유소).xls']
In [32]:
# concat 명령으로 합쳐본다.
tmp_raw = []

for file_name in stations_files:
    tmp = pd.read_excel(file_name, header=2)
    tmp_raw.append(tmp)
    
station_raw = pd.concat(tmp_raw)
In [33]:
station_raw.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 505 entries, 0 to 41
Data columns (total 10 columns):
지역       505 non-null object
상호       505 non-null object
주소       505 non-null object
상표       505 non-null object
전화번호     505 non-null object
셀프여부     505 non-null object
고급휘발유    505 non-null object
휘발유      505 non-null object
경유       505 non-null object
실내등유     505 non-null object
dtypes: object(10)
memory usage: 43.4+ KB
In [34]:
station_raw.head()
Out[34]:
지역 상호 주소 상표 전화번호 셀프여부 고급휘발유 휘발유 경유 실내등유
0 서울특별시 구천면주유소 서울 강동구 구천면로 357 (암사동) 현대오일뱅크 02-441-0536 N - 1496 1377 -
1 서울특별시 지에스칼텍스㈜ 동서울주유소 서울 강동구 천호대로 1456 (상일동) GS칼텍스 02-426-5372 Y - 1505 1345 -
2 서울특별시 지에스칼텍스㈜ 신월주유소 서울 강동구 양재대로 1323 (성내동) GS칼텍스 02-475-2600 N 1833 1509 1389 1150
3 서울특별시 SK네트웍스㈜암사주유소(self) 서울 강동구 올림픽로 749 (암사동) SK에너지 02-3427-2043 Y 1799 1519 1389 -
4 서울특별시 대성석유(주)직영 길동주유소 서울 강동구 천호대로 1168 GS칼텍스 02-474-7222 N 1798 1528 1398 1150
In [36]:
# 원하는 컬럼만 가지고 오고 이름도 다시 정의해서 stations 변수에 저장
# 이 단원에서는 휘발유만 다루겠다.
stations = pd.DataFrame({'Oil_store': station_raw['상호'],
                         '주소': station_raw['주소'],
                         '가격': station_raw['휘발유'],
                         '셀프': station_raw['셀프여부'],
                         '상표': station_raw['상표']
                        })
stations.head()
Out[36]:
Oil_store 주소 가격 셀프 상표
0 구천면주유소 서울 강동구 구천면로 357 (암사동) 1496 N 현대오일뱅크
1 지에스칼텍스㈜ 동서울주유소 서울 강동구 천호대로 1456 (상일동) 1505 Y GS칼텍스
2 지에스칼텍스㈜ 신월주유소 서울 강동구 양재대로 1323 (성내동) 1509 N GS칼텍스
3 SK네트웍스㈜암사주유소(self) 서울 강동구 올림픽로 749 (암사동) 1519 Y SK에너지
4 대성석유(주)직영 길동주유소 서울 강동구 천호대로 1168 1528 N GS칼텍스
In [37]:
# 추가로 주소에서 구 이름만 추출하자.
# 빈칸을 기준으로 분리 후 2번째 단어 선택
stations['구'] = [eachAddress.split()[1] for eachAddress in stations['주소']]
stations.head()
Out[37]:
Oil_store 주소 가격 셀프 상표
0 구천면주유소 서울 강동구 구천면로 357 (암사동) 1496 N 현대오일뱅크 강동구
1 지에스칼텍스㈜ 동서울주유소 서울 강동구 천호대로 1456 (상일동) 1505 Y GS칼텍스 강동구
2 지에스칼텍스㈜ 신월주유소 서울 강동구 양재대로 1323 (성내동) 1509 N GS칼텍스 강동구
3 SK네트웍스㈜암사주유소(self) 서울 강동구 올림픽로 749 (암사동) 1519 Y SK에너지 강동구
4 대성석유(주)직영 길동주유소 서울 강동구 천호대로 1168 1528 N GS칼텍스 강동구
In [39]:
# head만 보기에는 문제 없으나 500여개의 데이터가 어떤 상태인지 확인하기 쉽지 않다.
# unique() 검사 수행
stations['구'].unique()
Out[39]:
array(['강동구', '동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구',
       '양천구', '영등포구', '강북구', '용산구', '은평구', '종로구', '중구', '중랑구', '강서구',
       '관악구', '광진구', '구로구', '금천구', '노원구', '도봉구', '강남구'], dtype=object)

결과가 잘 나왔다. (책에서는 '구'에 '서울특별시'와 '특별시'라는 잘못된 데이터가 끼어 있어 처리하는 과정이 있다.)

In [40]:
# stations[stations['구']=='서울특별시']
In [41]:
# stations.loc[stations['구']=='서울특별시','구']='성동구'
# stations['구'].unique()
In [42]:
# stations[stations['구']=='특별시']
In [43]:
# stations.loc[stations['구']=='특별시','구'] = '도봉구'
# stations['구'].unique()
In [45]:
# 가격 컬럼의 unique를 구해보자.
stations['가격'].unique()
Out[45]:
array([1496, 1505, 1509, 1519, 1528, 1535, 1545, 1548, 1558, 1559, 1576,
       1598, 1668, 1727, 1933, 1442, 1445, 1455, 1457, 1458, 1464, 1467,
       1485, 1489, 1529, 1538, 1539, 1595, 1609, 1688, 1894, 1468, 1495,
       1499, 1514, 1544, 1565, 1625, '1607', '1617', '1639', '1641',
       '1658', '1780', '1798', '1799', '1849', '1859', '1879', '-', 1474,
       1479, 1534, 1649, 1975, 1448, 1490, 1497, 1506, 1536, 1567, 1569,
       1572, 1574, 1575, 1580, 1582, 1597, 1599, 1687, 1948, 1973, 1978,
       1999, 1454, 1498, 1549, 1560, 1589, 1657, 1720, 1769, 1848, 1898,
       '1428', '1442', '1452', '1459', '1468', '1497', '1498', '1499',
       '1509', '1519', '1529', '1537', '1539', '1543', '1569', '2135',
       1469, 1472, 1473, 1508, 1518, 1522, 1525, 1527, 1537, 1628, 1737,
       '1429', '1438', '1439', '1447', '1458', '1464', '1474', '1488',
       '1518', '1563', '1589', '1599', '1649', '1857', '1869', 1449, 1459,
       1523, 1587, 1785, 1835, 1869, 1879, 1890, 1895, 1412, 1429, 1466,
       1478, 1578, 1699, 1859, 1865, 1899, 1960, 1994, 2060, 2078, '1414',
       '1457', '1469', '1496', '1534', '1573', '1729', 1618, 1637, 1968,
       1758, 2069, 2089, 2093, 2145, 2162, 2229, 1434, 1437, 1477, 1483,
       1419, 1428, 1435, 1439, 1447, 1484, 1540, 1584, 1653, 1745, 1797,
       1627, 1465, 1486, 1488, 1615, 1443, 1517, 1532, 1768, 2064, 1475,
       1533, 1568, 1591, 1630, 1639, 1698, 1992, '1517', '1538', '1548',
       '1557', '1565', '1577', '1578', '1587', '1596', '1597', '1598',
       '1615', '1626', '1653', '1685', '1768', '1795', '1826', '1839',
       '1861', '1877', '1897', '1898', '1938', '1969', '1975', '1977',
       '1984', '1989', '1999', '2140'], dtype=object)
In [46]:
# 숫자가 아닌 '-' 문자가 끼어있다.
stations[stations['가격']=='-']
Out[46]:
Oil_store 주소 가격 셀프 상표
11 망원동주유소 서울 마포구 월드컵로 119 (망원동) - N SK에너지 마포구
12 (주)승지 염리동주유소 서울 마포구 백범로 126 (염리동) - N 현대오일뱅크 마포구
13 서강주유소 서울 마포구 독막로 134 (창전동) - N SK에너지 마포구
22 KR누리(주) 종암주유소 서울 성북구 종암로 145 (종암동) - N SK에너지 성북구
25 양천주유소(self) 서울 양천구 목동로 17 (신정동) - Y SK에너지 양천구
17 삼융주유소 서울 은평구 수색로 299 (수색동) - N SK에너지 은평구
40 동우주유소 서울특별시 강남구 봉은사로 311 (논현동) - N SK에너지 강남구
41 삼성주유소 서울 강남구 삼성로 521 (삼성동) - N SK에너지 강남구
In [47]:
# '-' 문자가 포함된 데이터 제외시켜 버리기
stations = stations[stations['가격'] != '-']
stations.head()
Out[47]:
Oil_store 주소 가격 셀프 상표
0 구천면주유소 서울 강동구 구천면로 357 (암사동) 1496 N 현대오일뱅크 강동구
1 지에스칼텍스㈜ 동서울주유소 서울 강동구 천호대로 1456 (상일동) 1505 Y GS칼텍스 강동구
2 지에스칼텍스㈜ 신월주유소 서울 강동구 양재대로 1323 (성내동) 1509 N GS칼텍스 강동구
3 SK네트웍스㈜암사주유소(self) 서울 강동구 올림픽로 749 (암사동) 1519 Y SK에너지 강동구
4 대성석유(주)직영 길동주유소 서울 강동구 천호대로 1168 1528 N GS칼텍스 강동구
In [48]:
# 가격을 float 형으로 변환한다.
stations['가격'] = [float(value) for value in stations['가격']]
C:\Users\whanh\AppData\Local\Continuum\anaconda3\lib\site-packages\ipykernel_launcher.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  
In [49]:
# 25개의 엑셀을 합쳤기 때문에 index가 중복될 수 있다.
# reset_index 명령으로 인덱스를 처음부터 다시 기록하자.
# index라는 컬럼이 하나 더 생기는데 그 부분을 제거한다.
stations.reset_index(inplace=True)
del stations['index']
In [50]:
stations.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 497 entries, 0 to 496
Data columns (total 6 columns):
Oil_store    497 non-null object
주소           497 non-null object
가격           497 non-null float64
셀프           497 non-null object
상표           497 non-null object
구            497 non-null object
dtypes: float64(1), object(5)
memory usage: 23.4+ KB

4-4 셀프 주유소는 정말 저렴한지 boxplot으로 확인하기

In [51]:
# 한글문제 해결
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

import platform

path = 'c:/Windows/Fonts/malgun.ttf'
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
    rc('font', family = 'AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('Unknown system... sorry~~~')
In [54]:
stations.boxplot(column='가격', by='셀프', figsize=(12,8));

boxplot으로 간편하게 셀프 컬럼을 기준으로 가격 분포를 확인할 수 있게 되었다. 코드의 결과를 보면 직사각형이 데이터의 대다수가 몰려 있는 곳이라는 것은 직관적으로 알 수 있다. 전반적으로 셀프 주유소인 경유가 가격이 낮게 되어 있다.

In [55]:
plt.figure(figsize=(12,8))
sns.boxplot(x='상표', y='가격', hue='셀프', data=stations, palette='Set3')
plt.show()

현대 오일뱅크, GS칼텍스, S-Oil, SK에너지 모두 셀프 주유소가 저렴하다. SK에너지는 그 중 가격대가 가장 높게 형성되어 있는 것을 알 수 있다.

In [56]:
plt.figure(figsize=(12,8))
sns.boxplot(x='상표', y='가격', data=stations, palette='Set3')
sns.swarmplot(x='상표', y='가격', data=stations, color='.6')
plt.show()

Swarmplot을 같이 그려보면 좀 더 확실히 데이터의 분포를 볼 수 있다. 셀프 주유소 말고 상표별 데이터를 확인했는데 SK에너지가 높은 가격대를 형성하는 주유소가 많았다. 전반적으로는 현대 오일뱅크가 4대 주유 브랜드 중에서는 저렴하다는 것을 확인할 수 있다.

이렇게 해서 셀프 주유소는 대체로 저렴하다고 이야기 할 수 있다. 여기서 한 단계 더 나아가서 서울시 구별 주유 가격, 서울에서 높은 가격의 주유소나 낮은 가격의 주유소에 대해서도 확인해보자.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함