분류 전체보기
- 윈도우10에서 윈도우7 시작메뉴 사용하기–Classic Shell 2018.03.10
- 4. 정적 웹페이지 웹크롤링 -> 웹 스크래핑 하기(scrapy) 2018.03.09 4
- 3. Scrapy 기본구조와 간단한 웹 스크래핑 2018.03.08 2
- 2. scrapy 및 selenium 설치하기(Windows용) 2018.03.07
- 1. 웹 크롤링(스크래핑)의개념과 Scrapy & Selenium 2018.03.06 1
- 23. pandas 추가 – 데이터 분석 by 그룹화 +피벗 테이블(pivot_table) 2018.03.06
- 22. pandas 추가 – 데이터 그룹화 함수 이해하기 2018.03.05
- 21. pandas 추가 – DataFrame 데이터 변형(중복행 제거/ 매핑/ 치환/ 카테고리 자료형) 2018.03.03 1
- 20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack)) 2018.03.02 3
- 19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating) 2018.03.01 1
- 18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium) 2018.02.28
- 참고 : matplotlib 분석 정리 2018.02.28
- 17. Matplotlib – 데이터 분석하기(3개) 2018.02.28 4
- 16. Matplotlib - figure, subplot-axes/ Plot모양 변형/ 눈금,레이블,범례 / 축의 범위 확인 및 변경 2018.02.27 1
- 15. Matplotlib 사용하기 –라인 플롯, 바 플롯, 히스토그램, 산점도 2018.02.25 1
윈도우10에서 윈도우7 시작메뉴 사용하기–Classic Shell
1. 네이버에서 [ classic shell ]을 검색하고 다운 받는다.
2. 설치 시, 다른 항목을 [x]를 만들고, 시작메뉴( Classic Start Menu)만 설치하도록 한다.
3. 설치 후 [ Start menu Style]에서 윈도우7Style을 선택한다.
4. [ Skin ] 탭에가서 원하는 스킨을 지정해준다. 윈7에 가까운 것은 [Windows Aero]스킨이다.
'개발공통 > IT기본' 카테고리의 다른 글
티스토리 단축키 추가하기 - [글 수정 단축키 m] (0) | 2019.01.02 |
---|---|
윈도우10 로그인 비밀번호 제거하기 (0) | 2018.03.14 |
C드라이버 용량 늘이기 2가지 방법 (0) | 2018.01.24 |
ISP(인터넷 서비스 공급자), NAT, 포트포워딩, IP추적 (0) | 2018.01.22 |
인터넷과 아이피 주소(IP address) (0) | 2018.01.21 |
4. 정적 웹페이지 웹크롤링 -> 웹 스크래핑 하기(scrapy)
정적 웹페이지를 크롤링하는 것은 scrapy만 사용할 것이다. py27 환경에서 pip install jupyter로 쥬피터 노트북을 설치해놓자.
데이터를 수집할 사이트는 https://www.rottentomatoes.com/top/bestofrt/?year=2015 이다.
영화리스트에서 하나를 선택하게 되면, 영화 상세페이지가 나오게 된다. 그 상세 페이지에서 제목, 평점 등의 정보를 가져오도록 해보자.
정적 웹페이지 Scray로 크롤링 하기
- Prompt에서 [scrapy 프로젝트]를 생성해줘야한다.
scrapy startproject rt_crawler
rt_crawler 라는 폴더가 생성되, 다시 똑같은 제목의 폴더가 있고, scrapy구조가 완성 된 것을 알 수 있다.
확인은 jupyter해서 하면된다.
우리는 이 *.py파일들을 수정한 뒤, spider를 실행하여, 최종적으로 스크래핑 및 크롤링을 할 것이다.
rt_crawler/
scrapy.cfg
rt_crawler/ # 해당 프로젝트의 Python 모듈
__init__.py
items.py # 프로젝트 Item 파일
pipelines.py # 프로젝트 Item pipeline 파일
settings.py # 프로젝트 Settings 파일
spiders/ # Spider 저장 폴더
__init__.py
...
import scrapy
#스크래파이의 Item클래스를 상속받는 클래스 생성
class RTItem(scrapy.Item):
#클래스의 객체에다가 정보를 저장할 필드(멤버변수)를 생성해주는데, title은 Field()형태로 저장할 것이다.
title = scrapy.Field()
score = scrapy.Field()
genres = scrapy.Field()
consensus = scrapy.Field()
#기본적으로 scrapy 임폴트
import scrapy
#우리프로젝트의 items.py에 만들어준 [RTItem 클래스] 도 import
from rt_crawler.items import RTItem
#scrapy에서 제공하는 Spider클래스를 상속한다.
class RTSpider(scrapy.Spider):
# 해당 Spider의 이름. 웹 크롤링 및 스크래핑을 실행할 때 여기서 지정한 이름을 사용함.
name = "RottenTomatoes"
#Spider로 하여금 크롤링하도록 허가한 웹 사이트의 도메인 네임.
allowed_domains= ["rottentomatoes.com"]
#웹 크롤링의 시작점이 되는 웹 페이지 URL. 해당 웹 페이지에서 출발하여 이어지는 웹 페이지들을 크롤링함
start_urls=[
"https://www.rottentomatoes.com/top/bestofrt/?year=2015"
]
#start_urls의 웹페이지를, spider가 서버에 요청하게 되고, 이에 대한 응답을 reponse라는 변수에 받는데, 이 reponse를 받아 처리하는 parse()함수를 정의
def parse(self, response):
#response로 얻어진 웹사이트의 어떤부분을 스크래핑할지 명시해줘야한다
어느 요소를 처리할지를 알기 위한 도구인 [ scrapy shell ] 과 [ 크롬 개발자도구 ]를 사용해서 찾아내야한다.
(1) 쥬피터를 띄우고있는 것 외에 새로운 prompt를 켜서, (py27)환경에서 scrapy shell을 실행시키자.
activate py27
scrapy shell “https://www.rottentomatoes.com/top/bestofrt/?year=2015”
(2) 다른 한켠에는, 해당 사이트에서 크롬 개발자도구를 켜서, 하이퍼링크를 담은 제목요소를 가리키는 xpath를 얻는다.
(3) 복사된 xpath를 scrapy shell에서 response.xpath(‘//*[@id="top_movies_main"]/div/table/tbody/tr[1]/td[3]/a’)를 입력해도 아무것도 안나온다.
<예외처리1>
- ***크롬 개발자도구의 xpath 추출기능이 만능이 아니라는 것이다.
*** 이 때, seletor가 얻어지는 xpath를 찾기 위해서, 뒤에서부터 한단계씩 지워서 거슬러 올라가면 된다.
(4) xpath의 마지막 뒤부분을 table까지 남기니까 selector가 리턴되었다!!!
response.xpath('//*[@id="top_movies_main"]/div/table')
(5) 이제 해당xpath에서 필요없는 경로를 삭제하면서 다시 최종적으로 타고 들어가야된다. 개발자 도구상 마우스로 살펴보면, table클래스 밑에 있는 <thead>와 <tbody>는 개별 item들과는 상관이 없는 요소였다. 그래서 xpath상의 tbody를 지운체, 다시 자식을 그대로 타고 가보았더니 최종적으로 원하는 데이터가 나왔다.
(a= 하이퍼링크요소, 까지 다시 타고 들어가야한다!)
response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a')
(6)이제, 크롬개발자 도구상에서 보면, 하이퍼링크요소 <a ~ > </a> 의 href라는 속성값이 하이퍼링크 url이다.
저 주소를 가져오기 위해서 xpath 마지막에[ /@href ]를 붙혀 주면, a요소의 자식속성 href를 Selector에 가져오게 된다.
바로 뽑아내기 위해서, href가 만약 리스트라면 [0]첫번째를 가져오고, extract()로 selector가 아닌 실제값을 추출하게 한다.
response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a/@href')[0].extract()
이 때, 맨마지막에 .encode(‘utf-8’)로 인코딩해주면, 앞에 달린 u가 사라진다.
response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a/@href')[0].extract().encode('utf-8')
print에 인코딩 없이 출력해도, 마찬가지로 u가 사라진다.(print에는 인코딩 기능까지)
실제 데이터를 추출해보면, u는 사라지므로 일단 남겨두자 - 이제 한 item의 상세 페이지의 서브url이 아니라, 전체 도메인까지 들어간 Full url이 필요하다.
서브url을 url변수에 저장하고, response.urljoin()함수를 이용해서, 자동으로 전체도메인에 서브도메인을 붙히자.
url = response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a/@href')[0].extract()
response.urljoin(url) - 이제 하나의 item에 대한 a요소로 찾은 상세페이지url이 아니라 전체 item들에 대한 url들이 모두 필요하다.
html구조를 거슬러 올라가서 찾아보자.
a요소 > td > tr까지 올라가보니까, tr이 반복되면서 , 리스트의 하나의item들이 각각 반복되는 것을 확인할 수 있다.
즉, <tr> : 리스트의 각 하나의 item, 3번째<td> : <a>요소에 제목과, 하이퍼링크속 Suburl을 포함한다. - 모든 tr요소들을 얻도록 xpath를 고쳐야한다.
즉, 각 item을 의미하는 <tr>요소의 인덱싱을 제거한 뒤, response.xpath(‘’)에다가 넣어서 셀렉터리스트로 반환받는다.
-> for문의 인자에 넣어서 각 item을 의미하는 tr의 셀렉터를 하나씩 꺼내도록한다.
–> 각각의 성분마다 .xpath(‘./’)을 이용해서 /@href 속성을 얻는 xpath경로를 셀렉터에 추가한 뒤 href변수(한 item의 href까지의 셀럭터)에 받는다.
-> href변수에 담긴 href속성 중 첫번째(서브url)을 [0].extract()한 것을 response.urljoin()을 통해 전체도메인에 붙혀서 url변수에 저장한다.
-> 확인을 위해 print(url)해준다. - 이제 100개의 리스트를 얻는 for문 부분을 복사해서 [rt_spider.py]의 response를 처리할 수 있는 parse()함수로 가져온다.
마지막의 print만 삭제 한 뒤, yield라는 키워드를 사용하여 scrapy의 함수 scrapy.Request함수를 사용하게 된다.
scrapy.Request(,)의 인자로는, 각 item의 하이퍼링크 full url을 넣어주고, 또다른인자 callback=에다가는,
url을 통해 불러온 페이지의 html소스코드를 어떻게 스크래핑할지 정해줄, 사용자 정의 함수를 넣어준다.
def parse(self, response):
#response로 얻어진 웹사이트의 어떤부분을 스크래핑할지 명시해줘야한다.
for tr in response.xpath('//*[@id="top_movies_main"]/div/table/tr'):
href = tr.xpath('./td[3]/a/@href')
url = response.urljoin(href[0].extract())
yield scrapy.Request(url, callback = self.parse_page_contents) - 이제 scrapy.Request()함수의 callback인자에 넣어준 parse_page_contents()함수를 정의해보자.
리스트의 각 url을 통해 불러온 페이지의 html소스코드를 어떻게 스크래핑할지 정해줄 것이다.
이 때, 인자로 들어가는 response는 scrapy.Request()의 각 상세페이지 하이퍼링크url을 통해 불러온 응답(html소스코드)을 의미한다.
parse()가 start_urls에서 얻은 웹사이트의 response를, xpath를 통해 어떻게 스크래핑할지 정의하였는데,
parse()속의 parse_page_contents()는 parse()함수의 마지막 Request(url, callback=)의 url에서 얻은 response를 어떻게스크래핑할지 정의하는 함수다. 그러므로, 파싱할 상세페이지를 shell과 크롬개발자도구로 먼저 살펴본 뒤, parse함수를 정의해준다.
(1) 현재 실행된 scrapy shell을 [ctrl+d]를 눌러서 종료해준다.
(2) 하나의 item 상세페이지url을 복사한 다음, scrapy shell을 다시 작동시키자.
scrapy shell https://www.rottentomatoes.com/m/mad_max_fury_road
(3) 이 상세페이지에서는 영화제목/평점/장르/총평의 정보를 가져올 것이므로, 크롬개발자도구를 이용해서 각각의 xpath를 알아야한다
In [33]: for tr in response.xpath('//*[@id="top_movies_main"]/div/table/tr'):
...: href = tr.xpath('./td[3]/a/@href')
...: url = response.urljoin(href[0].extract())
...: print(url)
리스트의 100개의 item에 대해, 하이퍼링크 url을 얻었다.
얻은 100개의 url을 가지고, 각 웹페이지를 요청하기 위해서, scrapy에서 제공하는 request함수를 사용할 것이다.
<하이퍼링크를 타고들어가서, 상세페이지의 item의 xpath를 통해, 필요한 요소들 뽑아오는 과정>
- (1)상세 페이지의 제목을 클릭후 xpath를 카피해, response.xpath()를 통해, selector를 얻어냈다면,
(2) /text()를 xpath에 더해서 제목을 가지는 셀렉터인지 확인하고,
(3) [0].extract()를 통해 텍스트만 뽑아내보자.
response.xpath('//*[@id="heroImageContainer"]/a/h1/text()')[0].extract()
(4) 이제 추출한 텍스트 앞에 있는 <\n과 공백>을 제거하기 위해서, 문자열에 대해서, .strip()함수를 적용시키면된다.
response.xpath('//*[@id="heroImageContainer"]/a/h1/text()')[0].extract().strip() - 비슷한 방법으로 영화 평점도 뽑아오자.
<예외처리2>
- 1개의 평점을 클릭한 뒤, xpath를 통해, 셀렉터를 뽑았는데 2개의 셀렉터가 나왔다.(아마도 크롬의 xpath뽑는 기준이 완벽치않음)
이 때는, 우리가 원하는 text()만 뽑은 다음, 결과물에 인덱싱[0]을 통해서, 첫번째것만 가져오는 식으로 한다. 그리고 extract()한다.
(기존에 항상 [0].extract()한 것과 동일함)
response.xpath('//*[@id="tomato_meter_link"]/span[2]/span/text()')[0].extract()
이렇게 scrapy shell과 크롬개발자도구로 [ 상세페이지의 원하는 정보를 스크래핑하는 코드 ]를 다 찾아낼 수 있다.
--> 여기까지 찾아냈으면, 앞서 정의했던 parse_page_contents()함수에다가 넣어줘야한다. - 이제 상세페이지의 각 필요한 정보들을 스크래핑하는 코드를
for문을 돌면서 각 웹페이지를 불러오는 곳(Request)의 callback인자를 통해 호출되는 parse_page_contents()함수에 넣는데,
처음 [items.py]에 정의한 양식대로, 객체를 만들어서, 거기다 담아야한다.(처음에 items.py의 RTItem클래스를 import했었다)
<예외처리 3> 장르 같은 경우, 리스트로 나온다.
- 장르 같은 경우, xpath로 셀렉터를 뽑아보면, < 공백가져서 각각 strip()필요한 2개 리스트 > 가 나와, 리스트형식으로 가져와야한다.
(1) xpath를 통해 셀렉터 뽑고, /text()를 통해 확인한다.
(2) 줄바꿈과 공백을 포함하더라도, 리스트로 나오기 때문에, [0].extract.strip()을 하면 안된다. (리스트는 .strip()도 안된다)
(3) extract()를 먼저하여 셀렉터 리스트 –> data리스트만 따로 변수에 담는다.
genres_list = response.xpath('//*[@id="mainColumn"]/section[3]/div/div[2]/ul/li[2]/div[2]/a/text()').extract()(4) 결국 parse_page_contents()함수에서 객체 속 하나의 필드에 저장해야하므로, 리스트를 ‘’구분자.join( 리스트)함수로 리스트를 합치면서,spider클래스의 parse()함수에서 나온 리스트형data는, join없이 그냥 리스트를 list변수에 담아두고, 변수에 map()으로 줄바꿈+공백만 삭제
각각에 strip()이 적용시킨다.
객체의 필드 = ''.join(genres_list).strip()
-> 뒤 pipelines에서 join해줄 것이다. - 총평(census)는 일단 생략하자. 그리고 마지막은 yield를 통해 채워진 객체 item을 반환해주자.
- 이제 우리가 제작한 spider를 실행시켜야한다.
(1) (py27)환경에서, 우리가 만든 프로젝트이름인 rt_crawler 폴더로 먼저 간다.
cd rt_crawler
(2) (py27) C:\Users\cho\Web_da\rt_crawler> 상태에서 크롤러를 실행시키는 명령어에 정의한 크롤러이름을 넣어 실행시킨다.
( 크롤러이름은,, 프로젝트명폴더/프로젝트명폴더/spiders에 만들어준 [rt_spider.py] 내 name = “RottenTomatoes” 를 넣어준다)
scrapy crawl RottenTomatoes
(3) 만약 csv파일로 저장하고 싶다면 scrapy crawl RottenTomatoes -o rt.csv
각종 indexError가 나서, 100개 다 스크래핑 못해오더라.
결과물을 프로젝트 폴더에서 확인해보면, RTItem에는 있는 필드지만, 스크래핑을 생략한 필드에 대해서는 , , 으로 비어서 나온다.
def parse_page_contents(self, response):
item = RTItem() #임폴트한 RTItem의 객체 생성
#RTItem클래스에 정의한, 객체에서 쓸, 필드(멤버변수)들은 열 인덱싱처럼 뽑아낸다.
item["title"] = response.xpath('//*[@id="heroImageContainer"]/a/h1/text()')[0].extract().strip()
item["score"] = response.xpath('//*[@id="tomato_meter_link"]/span[2]/span/text()')[0].extract()
genres_list = response.xpath('//*[@id="mainColumn"]/section[3]/div/div[2]/ul/li[2]/div[2]/a/text()').extract()
item["genres"] = genres_list = map(unicode.strip, genres_list)
yield item
여기까지가 [items.py] 와 [rt_spider.py] 정의가 끝났다.
scrapy shell을 종료시키자.
<예외처리 4>
한글로 주석달아서, 파이썬이 한글을 못읽는 문제 :
SyntaxError: Non-ASCII Character 관련된 에러라고 부릅니다. 코드 내에 한글을 파이썬이 읽어들이지 못해서 발생되는 에러
해당 *.py의 첫번째 줄or 두번째 줄에 : # -*- coding: utf-8 -*-
주석을 추가해준다.
<예외처리 5>
나같은 경우, 장르 리스트의 2번째 성분 앞에 있는 < \n 공백 >이 안사라졌다.
구글링 결과, list에 \n이라는 유니코드를 먼저 없어줘야한다.
genres_list = map(unicode.strip, genres_list) 를 통해서, \n을 먼저 사라지게 한 뒤,item["genres"] = ', '.join(genres_list).strip() 를 통해, 구분자로서 콤마(,)를 넣고, 공백도 제거하자.
--> join은,, 나중에 pipelinse에서 할 것이다.
웹스크래핑을 통해 얻어진 데이터를, 어떻게 가공하고, 외부파일로 저장할지 구체적으로 명시하기 위한 item pipelines
- 프로젝트 폴더> 프로젝트명 폴더 > [pipelines.py]를 열자.
(1)기존 클래스를 제거하고, csv모듈을 import한다.
import csv
(2) [RTPipeline]클래스를 정의한다.(object를 상속한다)
(3) 생성자함수( 자바에서는 클래스의 객체생성시, 필드들 초기화)를 정의해주는데, 생성자안에
import한 csv모듈을 이용하여, 매 행을 csv파일을 쓰도록 정의해줄 것이다. - 이제 새로운 함수 process_item()함수를 정의할 것인데, 인자로서는 self외에 item(객체)과 spider가 들어간다.
spider를 이용해 수집한 각 item들을 어떻게 처리할지를 명시하게 된다.
파이썬 리스트를 하나 생성한 뒤에, 각 item객체의 필드들을 append할 것이다.
genres는 리스트이므로 |문자를 구분자로 하여 .join으로 각각을 연결해준다.
다음에는 self.csvwriter.writerow를 통해 순서대로 쌓아둔 리스트(row)를 각 행에 밀어넣는다.
이제 객체를 사용한 item은 returnd을 해줘야한다. - 이제 정의한 item pipelines 를 크롤링시 사용하도록, settings에서 설정을 해줘야한다.
(1) [settings.py]를 열고, # ITEM_PIPELINES 부분을 찾아 주석을 해제한 뒤,
pipelines.py에 우리가 정의한 클래스(RTPipelines)를 명시해준 뒤 저장한다. - setting에서 크롤링시 자동으로 csv파일을 생성하여 쓰기 때문에,
scrapy crawl RottenTomatoes 뒤에 –o rt.csv를 적을 필요가 없다.
***나는 미리, 리스트를 join해버려서,,, 다시 join시키는 꼴이므로,, 각 글자마자.. |가 찍힌다.
*** pipelines에서 join함수를 사용할거면,
rt_spider.py에서, 각 필드에 담을 때는,, join없이, 그냥 리스트를 유지하고, \n등 유니코드만 제거해준다.
my) spider에서 객체에 xpath를 통해 추출해서 담을 때는, 리스트는 유지하고 join쓰지말고, map()으로 유니코드만 제거 + yield로 item리턴
pipelines에서 객체의 필드를 꺼내 쓸때는, 리스트의 경우 구분자와 join을 써서 각 항목 보기좋게 구분 + return으로 item리턴
#csv모듈 임폴트
import csv
#object를 상속하는 RT크롤러의 pipelines클래스 정의
class RTPipelines(object):
def __init__(self):
#생성자에는 self를 이용해 변수를 만들고, csv모듈의 writer함수를 이용해서, csv파일을 열고 쓸 것이다.(없다면 생성)
self.csvwriter = csv.writer(open("rt_movies_new.csv", "w"))
#이 순서대로 csv파일의 열들을 채워넣는겠다는 의미로서, 열을 직접 입력해준다.
self.csvwriter.writerrow( ["title", "score", "genres"] )
def process_item(self, item, spider):
row = []
row.append(item["title"])
row.append(item["score"])
row.append('|'.join(item["genres"]))
self.csvwriter.writerow( row )
return item
최종 결과물
'빅데이터 관련 프로그래밍 > 웹 크롤링 - scrapy & selenium' 카테고리의 다른 글
참고 : 2가지 크롤링 요약 (0) | 2018.03.11 |
---|---|
5. 동적 웹페이지 웹 스크래핑하기(scrapy+selenium) + 파이썬3.6에 설치 (8) | 2018.03.11 |
3. Scrapy 기본구조와 간단한 웹 스크래핑 (2) | 2018.03.08 |
2. scrapy 및 selenium 설치하기(Windows용) (0) | 2018.03.07 |
1. 웹 크롤링(스크래핑)의개념과 Scrapy & Selenium (1) | 2018.03.06 |
3. Scrapy 기본구조와 간단한 웹 스크래핑
Scrapy의 기본구조
- Spider는 어떤 웹 사이트들을 어떠한 규칙에 의거하여 크롤링할 것인지 명시하고, 각각의 웹 페이지의 어떤 부분을 스크래핑할 것인지 등을 일괄적으로 명시하는 클래스이다. 즉, 웹크롤링과 웹스크래핑 규칙을 설정하는 핵심요소
- Spider 상에 웹 페이지 상의 어느 부분을 스크래핑할 것인지 명시하고자 할 때, 특정 HTML 요소를 간편하게 선택할 수 있도록 하는 메커니즘을 scrapy에서는 Selector 클래스로 구현하였다.
Selector를 사용하면 CSS 선택자를 직접 사용하여 특정 HTML 요소를 선택할 수도 있으나, HTML 상에서의 특정 요소를 선택하는 데 특화된 언어인 'XPath'를 사용하는 것이 더 권장된다. HTML과 CSS의 기초를 잘 알고 있다면, XPath는 몇 개의 예제를 따라서 작성하는 것만으로 금방 학습할 수 있습니다. 만약 XPath에 대해 좀 더 자세하게 배우고 싶다면, 다음의 링크를 참조하시길 바란다.
http://www.w3schools.com/xsl/xpath_intro.asp - Item은 scrapy에서 기본 제공하는 자료구조 클래스입니다. 새로운 Item 클래스를 정의하고 여기에 우리가 수집하고자 하는 정보들을 명시하면, Spider 상에서 실제 스크래핑을 수행한 결과물을, 파일형태로 저장할 때, item을 매개로 사용해서, 간편하게 관리할 수 있다.
- Item pipeline 클래스를 새로 정의하고 여기에 각 Item들을 어떻게 처리할 것인지 명시하면, 해당 규칙에 의거하여 데이터를 가공하거나 혹은 외부 파일로 간편하게 저장할 수 있다.
- Spider나 Item pipeline 등이 어떻게 동작하도록 할 지에 대한 세부적인 설정 사항을 Settings 상에 명시한다
거의 모든 웹 사이트에서는 크롤링을 수행하는 크롤러 봇들의 행동을 제한하고자 robots.txt라는 파일을 게시한다.
https://www.flearning.net/robots.txt 처럼, robots.txt에는, 크롤러들이 ~어떻게 행동하라~ 라고 명시되어있다.
예를 들어, 우리가 제작한 Spider가 이 robots.txt 파일에 명시된 규칙을 따를 것인지, 혹은 무시할 것인지 등을 Settings 상에서 설정할 수도 있습니다. 서버에서 차단당하기 전에, 완급조절도 Settings상에서 설정가능하다.
Scrapy shell과 크롬 개발자 도구를 사용하여 웹 스크래핑 체험하기
1. Anacond Prompt 에서 activate py27로 파이썬 2.7가상환경을 활성화 시킨 상태로 만들자.(scrapy는 py3버전을 지원하지않는다)
activate py27
2. 지정한 웹 페이지를 스크래핑한 결과를 인터랙티브하게 확인할 수 있도록 하는 기능인 scrapy shell을 실행해보자.
scrapy shell http://gall.dcinside.com/board/lists/?id=oriental_medicine
3. 웹사이트의 제목을 가져오기 위해서, 페이지 소스보기를 눌러서 어떤 계층을 타고내려왔는지 찾아야하는데,
크롬에서 제공하는 개발자 도구[F12]를 사용해서 더 간편하게 할 수 있다.
개발자도구에서 [Elements]를 클릭하면 html소스를 볼 수 있고, [좌측상단 마우스]아이콘 클릭 후 가져올 부분을 클릭하면,
html 코드가 하이라이트로 표시된다.
이 상태에서 하이라이트된 코드를 [우클릭 ] > copy > [ xpath copy]를 선택하여 해당 html코드를 xpath형태로 복사해오게 된다.
4. 다시 scrapy shell 로 돌아와서 , response.xpath(‘’) 함수를 미리 쳐놓고, 작은따옴표 사이에다가 복사한 xpath를 붙혀넣는다.
이 때, response라는 것은, scrapy shell작동시 적은 url에 웹페이지 요청을 보낸 것에 대한 응답을 담고 있는 변수이다.
특정요소의 xpath를 xpath함수의 인자로 넣으면, xpath에 부합하는 해당요소를 나타내는 selector 객체를 리턴받을 수 있다.
( 디씨인사이드는 js나 ajax로 만든 동적웹페이지라서,, selector 반환이 안된다. 그래서 stackoverflow로 바꿨다.)
이 때, data= 이후부분의 정보를 긁어오는 것이다.
<Xpath>
xpath(‘’)안에 들어가는 xpath인자에서
// 는 자손요소들을 모두 살펴본다(가장 상위의 html의 )
/ 는 /앞부분의 직계자식 요소만 살펴보는데, /뒷부분의 조건에 맞는 놈을 찾는다.
* 는 모든 종류의 태그를 찾는다.
[@id =”” ] id값 조건을 명시하는 부분이다. 클래스라면 @class=”” 형식으로 준다.
[1] 대괄호는 인덱싱이다. 주의할 점은 xpath안의 html인덱스는 정수 1부터 시작한다!!
5. xpath인자의 마지막에 /text()를 붙히고 xpath함수를 호출하면, 텍스트만 뽑아내서 selector에 담긴다
그 뒤에 extract()함수까지 적용하면 Selector에 담긴 텍스트를 추출해서 표시한다.
response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()
원래 selector에는 리스트형식으로 담긴다. 여기서는 제목 1개부분만 추출하였으므로, extract() 추출한 리스트 중 첫번째[0]을 호출한 것과 동일하다.
response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()
response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()[0]
6. 이제 제목을 하나하나 가져올 것이 아니라, 전체 리스트로 가져올 방법을 연구해보자.
개발자 도구의 하이라이트 부분의 더 상위부모방향으로, 마우스로 타고 올라가다 보면, 제목의 위치에서, 하나의 item전체를 정의해주는 <div 요소 class가 있을 것이다. 그것을 기준으로 닫아보면, 각 제목들이 부여되 된 것을 확인할 수 있다.
나의 경우, question-summary narrow라는 div요소가 각 질문item들을 정의해주었다.
7. 그러면, 처음에 제목을 가진 xpath로 돌아가서, /text()부분을 제거하고, 다시 살펴보자.
//*[@id="question-summary-49154165"]/div[2]/h3/a/
이제 뒷부분부터 단위를 줄여가면서 response.xpath()를 호출하여 data='부분에 해당 div요소(question-summary narrow클래스)가 걸릴 때까지 줄여가보자. 나같은 경우, 뒷부분에서부터 줄여서 갈 수 있는 div의 위치가 div[2]까지 밖에 없다.
response.xpath('//*[@id="question-summary-49154165"]/div[2] ‘)
8. 이제 내가 선택한 제목은 2번째글의 제목이므로, div[2]로 인덱싱 되어있다. 인덱싱부분을 지우고 .xpath()를 호출하여,
전체 질문item 대한 셀렉터리스트를 가져오자. 그리고 가져온 selector리스트를 파이썬 리스트 titles에다가 저장하자.
( 나는ㅠㅠ xpath 자체가,, class 뿐만아아니라 id에 글번호가 표시되어있고, xpath 카피시,, id로 검색되게된다.)
(이 때, 사이트를 바꾸고, id대신 class로 검색하게 했더니—> 모든 질문item에 대한 글 제목까지 한꺼번에 나왔다.)
scrapy shell “https://stackoverflow.com/questions”
response.xpath('//*[@class="question-summary"]/div[2]/h3/a')
titles = response.xpath('//*[@class="question-summary"]/div[2]/h3/a')
9. titles에 인덱싱을 해서 첫번째를 한번 뽑아보자.
title = titles[0]
title
10. 이제 원래는 titles리스트는 각 item전체의 리스트고, 진짜 title까지 내려가줘야한다
나는 id대신 class로 검색하고, 자식요소들 다 가지고 있으니까, 바로 각 item들의 제목 리스트가 되었다.
원래대로라면 명시하는 방법은 .xpath(‘./’)함수로 ( 맨처음 글제목을 긁어올 때 들어간)자식요소를 줄 수 있다. 나는 맨 마지막 /text()를 주면 된다.
이 때, 쩜/로 기존 xpath에 이어줘야한다.
title.xpath('./text()')
extract()로 data(텍스트)부분만 추출해보자.
title.xpath('./text()').extract()
11. 이제 반복문을 통해 첫번째만이 아니라. titles 전체에서 제목만 뽑아내자
for title in titles :
...: print title.xpath('./text()'
...: ).extract()
글 1개의 제목 요소 찾아서 /text() 로 확인 –>xpath뒷부분 제거하여 item 1개의 div요소 –> 인덱싱을 제거하여 전체 item selector 추출 –> 리스트 titles에 담기-> for문을 통해 각 글 1개의 제목까지 자식요소 복구 후 .xpath( './ 자식요소 / text() ' ).extract()로 다뽑기 or []인덱싱하기
'빅데이터 관련 프로그래밍 > 웹 크롤링 - scrapy & selenium' 카테고리의 다른 글
참고 : 2가지 크롤링 요약 (0) | 2018.03.11 |
---|---|
5. 동적 웹페이지 웹 스크래핑하기(scrapy+selenium) + 파이썬3.6에 설치 (8) | 2018.03.11 |
4. 정적 웹페이지 웹크롤링 -> 웹 스크래핑 하기(scrapy) (4) | 2018.03.09 |
2. scrapy 및 selenium 설치하기(Windows용) (0) | 2018.03.07 |
1. 웹 크롤링(스크래핑)의개념과 Scrapy & Selenium (1) | 2018.03.06 |
2. scrapy 및 selenium 설치하기(Windows용)
scrapy사용을 위한 파이썬 2(2.7)버전 설치하기
scrapy는 파이썬3에서는 제공되지 않으므로, 웹 스크래핑 & 크롤링시에는 파이썬 2.7를 설치해서 사용해야한다.
1. Anaconda Prompt를 켜고, conda명령어로 가상의 파이썬툴 환경을 구성할 수 있다. 여기에 python 2.7을 설치해준다.
py27이라는 가상환경이 생성된다.
conda create –n py27 python=2.7
2. 만들어놓은, py27이라는 가상의 파이썬툴을 activate시켜야한다. 아래 명령어를 실행하면, (py27)환경에서 파이썬이 준비되어있게 된다.
activate py27
3. 파이썬 버전을 확인해보자.
python --version
4. 만약 웹스크롤링 & 스크래핑 작업이 끝났으면, deactivate 시켜서 다시 python3환경 (base)로 돌아와야한다.
deactivate
scrapy 설치를 위해, 추가 파이썬 라이브러리(lxml + pypiwin32 + c++compiler) 설치하기
1. https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 사이트에 들어간 뒤, cp27-cp27- 64비트를 찾아서 다운로드한다.
.whl 확장자는 수동설치 파일이다. 파이썬 2.7버전이기 때문에 cp2727을 받은 받는 것이다.
2. 다운받은 whl파일을, 설치시 편의를 위해, 홈폴더인C:\Users\cho 에 갖다놓는다.
3. 이제 Prompt에서, (py27)환경을 activate된 상태에서, pip install + 파일명을 통해 인스톨한다. 이때, tab을 이용해서 자동완성 시키자.
4. 윈도우에서 필요로하는, pypiwin32를 설치해야한다. 다운 받은 whl파일에 대한 라이브러리 설치를 한다.
pip install pypiwin32
5. Microsoft Visual C++ complier for python 2.7 를 다운받고 설치해주자.
http://www.microsoft.com/en-us/download/details.aspx?id=44266
Scrapy 설치
1. prompt에서 pip install Scrapy를 쳐서 설치한다.
Selenium 설치
1. prompt에서 pip install selenium 를 쳐서 설치한다.
2. selenium은 자동화 브라우저 라이브러리이다. 그 브라우저를 사용하기 위한 드라이버 프로그램이 필요로한다.
크롬에서 제공하는 크롬드라이버를 windows용 가장 최근버전을 다운 받자.
https://sites.google.com/a/chromium.org/chromedriver/downloads
압축을 풀고, 드라이버파일을, 홈폴더로 옴긴다.
Selenium을 사용할 때 마다, 드라이버 실행파일을 호출해야한다.
Scrapy 테스트하기
1. (py27)환경을 activate 한 상태에서, scrapy shell “웹사이트 주소” 형식으로 호출해보자.
scrapy shell https://www.flearning.net/courses/6
웹 페이지를 대상으로 scrapy에서 shell형태의 또다른 인터렉티브 프로그램을 제공한다.
2. shell에다가 response.text 명령어를 입력하면, html소스코드를 가져오는 것을 확인할 수 있다.
3. 확인이 끝나면 exit()명령어로 나오면 된다.
Selenium 테스트하기
1. 먼저 (py27)환경에서도 ipython을 새로 설치해야한다. (그렇지 않으면 2.7상태에서 ipython 치면 3.6버전으로 인식된다)
conda install notebook ipykernel
2.(py27)환경에서 ipython으로 진입한다
3. selenium이라는 모듈에서, webdriver를 import한다.
from selenium import webdriver
4. webdriver라이브러리를 이용해서, 크롬드라이버를 실행시키고, 그것을 변수 browser에 저장하자. 새로운 크롬 브라우저가 뜨는 것을 알 수 있다
browser = webdriver.Chrome("C:/Users/Cho/chromedriver.exe")
5. 자동화 브라우저를 이용해서, 웹페이지를 띄울 수 있다.
browser.get(http://asdfkakd.com)
6. 다 사용하고 나면 끌 수 있다.
browser.quit()
'빅데이터 관련 프로그래밍 > 웹 크롤링 - scrapy & selenium' 카테고리의 다른 글
참고 : 2가지 크롤링 요약 (0) | 2018.03.11 |
---|---|
5. 동적 웹페이지 웹 스크래핑하기(scrapy+selenium) + 파이썬3.6에 설치 (8) | 2018.03.11 |
4. 정적 웹페이지 웹크롤링 -> 웹 스크래핑 하기(scrapy) (4) | 2018.03.09 |
3. Scrapy 기본구조와 간단한 웹 스크래핑 (2) | 2018.03.08 |
1. 웹 크롤링(스크래핑)의개념과 Scrapy & Selenium (1) | 2018.03.06 |
1. 웹 크롤링(스크래핑)의개념과 Scrapy & Selenium
웹 크롤링과 웹 스크래핑
웹 스크래핑(web scraping) : 웹 사이트 상에서 원하는 부분에 위치한 정보를 컴퓨터로 하여금 자동으로 추출하여 수집하는 기술
웹 크롤링(web crawling) : 자동화 봇(bot)인 웹 크롤러가 정해진 규칙에 따라 복수 개의 웹 페이지를 브라우징 하는 행위
링크를 따라 돌면서, 연결된 페이지를 가져오는 과정 : 웹 크롤링
웹 크롤러가 가져오는 하나의 웹 페이지가 있을 때, 추출하길 원하는 항목의 위치를 지정해서, 데이터로 가져오는 것 : 웹 스크래핑
웹 크롤링 및 스크래핑을 위한 Python 라이브러리 : Scrapy
웹사이트를 크롤링 및 스크래핑을 통해 정보를 추출하고, 이를 데이터셋의 형태로 저장하는데 특화된 라이브러리
*문제 : 기초적인 기능만 사용하면, 보고있는 화면을 그대로 스크래핑 할 수 없다. ex> 동적 웹 페이지, 요청시 로그인 정보 함께 보내는 웹 페이지
(ex1) https://www.premierleague.com/tables?co=1&se=42&mw=-1&ha=-1 사이트의 경우
처음에는, 기본적인 정적 웹 페이지(html/css로만 구성)을 띄운 다음, 곧바로 사용자 요청에 의해 드랍박스대로 서버에 요청해서 동적으로 웹페이지가 바뀐다. 만약 여기서 Scrapy만 사용한다면, 처음 잠시 띄워진 정적웹페이지만을 가져온다는 단점이 있다.
(ex2) 로그인 정보를 한꺼번에 보내야하는 웹 페이지의 경우이다. 회원에게 제공되는 웹페이지를 보고 싶을 때, 현재 회원정보로 로그인 되어있다는 정보를 함께 보내야한다. 이것을 전문용어로 쿠키라 한다.
로그인한 상태에서는 메뉴와 강의듣기가 달라진다.(로그인 전 에는 로그인하라고 페이지가 뜬다)
이것은, 요청시, 서버에 쿠키를 같이 보냈기 때문이다.
만약 여기서 Scrapy만 사용한다면, url에 쿠키를 같이 보낼 수 없다.
파이썬 웹브라우저 자동화 라이브러리 : Selenium
Selenium에서 제공하는 webdriver 모듈을 사용하여, 동적 웹페이지나 로그인정보를 담아 서버에 요청하는 작업을 할 수 있게 한다.
Scrapy 기초기능의 단점을 보완한다.(고급기능까지 쓰면 되지만, html/css/js의 이해도가 필요하다)
'빅데이터 관련 프로그래밍 > 웹 크롤링 - scrapy & selenium' 카테고리의 다른 글
참고 : 2가지 크롤링 요약 (0) | 2018.03.11 |
---|---|
5. 동적 웹페이지 웹 스크래핑하기(scrapy+selenium) + 파이썬3.6에 설치 (8) | 2018.03.11 |
4. 정적 웹페이지 웹크롤링 -> 웹 스크래핑 하기(scrapy) (4) | 2018.03.09 |
3. Scrapy 기본구조와 간단한 웹 스크래핑 (2) | 2018.03.08 |
2. scrapy 및 selenium 설치하기(Windows용) (0) | 2018.03.07 |
23. pandas 추가 – 데이터 분석 by 그룹화 +피벗 테이블(pivot_table)
county_facts.csv
primary_results.csv
필요한 패키지 import
%matplotlib nbagg
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
데이터 2개 읽기
primary = pd.read_csv("python_da/data/2016_presidential_election/primary_results.csv", sep=",")
counties = pd.read_csv("python_da/data/2016_presidential_election/county_facts.csv", sep=",")
primary에는, 미국내 주 - 카운티 별, 각 정당, 후보자의 데이터 및 득표율 데이터
counties라는 primary의 칼럼 중 fips라는 코드를 식별자를 하여, 각 유권자별 데이터가 나와있다.
head()와 shape와 columns까지 확인해보자.
데이터 분석-1 : 각 후보별 전체지역 득표수
각 후보들별로 전체지역 득표수를 계산해보자.
이전 방식이라면 후보를 의미하는 candidate의 유니크한 값들을 추출한 다음, 반복문을 돌리면서 마스크로 votes열만 뽑아 낸 뒤, 통계량을 계산했다.
데이터 그룹화기능을 사용하면, 이를 한 줄에 끝낼 수 있다.
( 각 ~별 -> groupby의 기준열로 들어간다.)
( ~를 계산 -> 데이터그룹화의 결과물에 [ "칼럼명" ] 으로 추출한다)
- 각 후보별 득표수를 계산할 것이므로, 각 후보의 unique한 이름들만 확인해준다.(분석과정에서 필요한 것은 아니다)
primary["candidate"].unique() - primary 전체 DataFrame에 대해서 groupby()를 할 것인데, 기준열이 candidate열 이다.
각 후보별로 데이터를 계산하므로,groupby()의 기준열로 쓰면, 각 성분인 후보들을 기준으로 데이터가 그룹화되서 계산할 수 있다.
그룹화된 결과물에서 DataFrame 전체가 아니고, votes열만 필요하므로, 그룹화된 결과물에 열인덱싱 처리를 해준다.
마지막으로, 득표수를 계산하려면, 그룹별로 votes열의 합을 구해야하기 때문에 sum()함수를 적용한다.
primary.groupby("candidate")["votes"].sum() - 투표수(성분)별 오름차순으로 보기 좋게 정렬하기 위해서, 맨 뒤에 .sort_values()적용해서 새로운 변수에 담아준다.
(변수에 담아야지, plot을 그릴 수 있다!)
이 때, by= 정렬기준열 을 명시하지않은 것은, 열이 하나(votes열) 밖이기 때문이다.
candidate_to_votes_s = primary.groupby("candidate")["votes"].sum().sort_values() - 후보별 전체지역 득표수을 정렬한 것을, 수평 바 plot으로 나타내자.
candidate_to_votes_s.plot(kind="barh", fontsize=8)
(my : 각 ~~ 별 –> ~~열을 groupby()함수의 기준열로 삼아라! , a와 b의 ~ –> a + b (배반)으로 포함하는 열을 기준열로 삼아라!)
데이터 분석 – 2 : 각 주별, 공화당과 민주당의, 득표비율 계산
다시 데이터의 head()를 보자.
primary.head()
각 주별 ----------> state열이 기준열1
공화당과 민주당 –> party열이 기준열2
2가지 열을 groupby()함수의 [기준열]로 사용할 것이다.
득표비율 --------> votes열을 데이터그룹화의 [추출열]로 사용할 것이다.
- 데이터그룹화의 기준열을 state, party순으로 주어서, 각 주별 – 공화당/민주당 – 득표수(합)을 계산해보자.
계층적인덱스가 주 – 당 형태의 Series가 얻어질 것이다.
아직까지 우리가 원하는 모양(공화당과 민주당의 각 주별 득표비율)이 아니므로, 변수에 저장해주자.
state_party_votes_s = primary.groupby( ["state","party"] ) ["votes"].sum() - 비율을 구하기 위해서, 각 주의 전체 득표수를 나누어줘야한다.
기준열에 state열만 넣어서, votes열을 추출하여 sum()함수를 쓰면, 각 주별 전체 득표수가 나오므로
계산해서 변수에 담아주자.
state_to_votes_s = primary.groupby("state")["votes"].sum() - 이제 1에서 구한 Series를 2에서 구한 Series로 나눠준다.
이렇게 나누기(연산)을 할 때, 계층적 인덱스 中 가장 첫번째 층의 인덱스를 기준으로 계산되기 때문에,
1에서 기준열을 정할 때, state –> party 순으로, state열을 먼저 지정해야하는 것을 알 수 있다.
계산한 값을 새로운 변수에 담고 head()를 확인해보자.
state_party_to_vote_pcts_s = state_party_votes_s / state_to_votes_s
state_party_to_vote_pcts_s.head() - 이제 비율을 바탕으로 bar plot을 그려보자.
이 때, 민주당/공화당(2번째 층 인덱스)를 unstack()으로 컬럼으로 올리고 barplot을 그리자.
컬럼으로 보내면, 인덱스인 각 주(state)가 수평y축에 , 칼럼인 [민주/공화당]이 범례에 나타나면서 각 주별 2개의 막대가 생길 것이다.
이 때, stacked=True까지 주면, 이 나타나면서 막대에 합쳐진, 비율을 가지게 될 것이다.
간단히 분석해보자면, 대부분의 주가 공화당이 득표가 우세하고, 몇몇 주는, 민주당이 장악하였다.
my) 각 주별, a와 b의 -> 기준열로 사용할 것. 2개 주제 이상이면, 기준열 순차적으로
~를 계산 -> 데이터그룹화(groupby)의 결과물에 [""]열인덱싱으로 필요한열 추출
최종 결과물에 index-> barplot의 index / columns -> 한index에 세워지는 bar막대기 갯수 및 범례의 갯수
데이터 분석3 – 사용자 정의함수를 이용하여,
각 county별, 당선된 후보의, 백인유권자의 비율을 계산하기 -> 정당별, 당선된 후보들의 백인유권자 비율 보기
정치권에서 속설 중, 백인유권자들이 많은 지역일수록 ---> 공화당 후보가 당선될 확률이 높다라는 것이 있다. 이것을 증명하기 위해서
primary데이터에서 각 county별, 당선된 후보의 백인유권자의 비율을 계산해서 증명해보자.
즉, 각 county별로 그룹화 ->
각 그룹에 대해 투표수가 가장많은 행(당선자를 가진 행) 뽑는 함수 적용(사용자 정의함수) -> 여기까지는 각 county별 당선후보 데이터(행)을 뽑은 것
다른데이터에 있는 county별 백인유권자 정보를, 기준열(county = fips열)을 가지고 merging -> 붙혀진 칼럼명 이름바꾸기->
각 county별 당선후보에 대한 백인유권자비율을 구해진 상태다. 이제 이 데이터에서 ,
각 정당별, 당선후보자들의 (그룹화 기준 : party, candidate) 백인유권자비율(추출열)의 평균(통계함수)을 구하자.->
barplot 그리고 범위 및 index다 보이도록 수정하기
이번에는 집계함수를 직접 정의하여, 집계를 수행해보자.
- 통계(집계)함수를 정의하는데, func이라는 함수는, 데이터그룹화의 결과물(agg_df)에 대해, sort_values()함수를 이용해 votes열을 내림차순으로 정렬하고, 그 중 첫번째 성분을 뽑는 .iloc[0]으로 인덱싱하여,
대입되는 그룹화결과물의, votes열에 내림차순 정렬 후 1번째 행만 뽑기 = 가장 득표수가 많은 행을 뽑아낼 것이다.
func = lambda agg_df: agg_df.sort_values("votes", ascending=False ).iloc[0]
(당선된 후보 = 투표수가 많은 후보 = 투표열을 내림차순 정열후 1번째 행인 후보) - primary의 head()를 다시 한번 살핀 뒤, county를 대변하는 코드인 fips열을 그룹화 기준열로 사용해서 그룹화하고,
그룹화된 결과물에 사용자 정의함수를 사용할 수 있게 해주는 .agg()함수에다가,
그룹화결과물을 인자로 받아 votes열을 추출하여 votes열의 가장 큰 수(득표수가 가장 많아 당선)를 뽑아내는 func 함수를 인자로 주자.
그럼 primary를 fips(county)별로 그룹화한 결과물에, func함수를 적용하여, 각 county별 <votes순으로 내림차순하여, 득표수가 가장 큰 행이 후보이름 + 당명과 함께>을 뽑아내진다.
이것을 winners라는 변수에 담아서 살펴보자.
winners = primary.groupby("fips").agg(func)
winners
살펴보면) 각 county별 votes수가 가장 많은 행을 뽑아서 –> 그 county에 가장 많은 득표를 얻은 사람 + 당 까지 알 수 있다. - primary데이터에서 얻은 winners라는 각 county별 최다득표 후보와 당 데이터에다가
각 county별 백인유권자 정보를 붙혀넣어야한다!
앞서 read한 counties라는 df에서 얻어와야한다. columns 중에 RHI825214라는 칼럼이, 해당 county별 백인 윤권자 비율이다.
counties["RHI825214"].head()
이winnders와 counrties의 county에 대한 행의 갯수가 서로 다르나, 병합의 key열(들or인덱스)을 지정해서,
각각 동일한 기준성분에 대해 m x n형태로 합쳐주는 것을 pd.merge()함수라고 배웠다.
merging을 이용해서, fips를 기준으로 winner에다가county의 백인유권자 정보를 병합시켜보자.
(한쪽 데이터를 고정시켜서 병합시킬 때는, how=left or right인자) - 이제 counties에서는 fips열과 rhi825214열만 병합시킬 것이므로, merge()의 인자에서, 열인덱싱을 리스트형식으로 넣어준다.
그리고, 데이터를 확인해보면, winners에서는 fips가 그룹화의 기준이 된 결과 index로 있고 / counties에서는 fips가 열이므로,
pd.merge()의 인자에 left_index=True, right_on="fips" 로, [df의 인덱스 <----> 특정열2개 중 1열]이 서로의 병합기준이 된다고 지정해줘야한다.
how="left"인자를 통해서, left인 winners 에다가 병합시킬 것이다.
winners_county_races = pd.merge(winners, counties[ ["fips", "RHI825214"]],
left_index=True, right_on="fips", how="left")
새로운 데이터를 보게되면, 인덱스에 있던 fips가, 새로운데이터와 겹치는 부분으로 mxn형태로, 열로 왔고 우측에는 백인유권자 비율이 붙은 것을 확인할 수 있다. - 이제 백인유권자 비율의 컬럼(RHI825214)의 이름을 바꿔주자. rename()함수의 인자로, 컬럼 = {딕셔너리}형태로 바꿔준다.
이것을 다시 기존 데이터에 대입하면 바뀐 데이터가 된다.
winners_county_races = winners_county_races.rename(columns={"RHI825214" : "white_pcts"})
winners_county_races.head() - 이렇게 얻어진, 각 county별 최다득표자(winner)의 백인유권자 비율까지 표시된 데이터에다가,
party와, candidate를 그룹화 한뒤, 각 정당별 후보자들의 백인유권자 비율의 평균을 계산해보자.
( 각 county별 최다득표자들만 모았으니, 각 county별 당선자들이다. 이제 county를 잊고, 정당 별 후보자=당선자의 백인비율을 보는 것이다)
winners_county_white_pcts = winners_county_races.groupby(["party", "candidate"])["white_pcts"].mean()
winners_county_white_pcts - 수평 바 플롯을 그려보자.
winners_county_white_pcts.plot(kind="barh", fontsize=8) - .plot()을 그린 좌표평면 변수를 생성해서, 몇가지 수정해 이쁘게 보이도록 하자.
ax = winners_county_white_pcts.plot(kind="barh", fontsize=8)
(1)먼저, x축의 범위를 50부터 100까지 보자.(0~50까지는 모두 차 있으니까)
(2) index가 다 안보일 때는, 이 함수를 호출하자 : plt.tight_layout()
ax.set_xlim([50,100])
plt.tight_layout()
간단히 살펴보면 ) 공화당 후보(Rep.)가 당선된 county에 백인유권자의 비율이 75%으로 높긴 높다.
그런데 특이한 점은, 민주당 후보에서도 하나의 county에 백인유권자의 비율이 80%이상으로 높다.
피벗 테이블(pivot_table)로 데이터 분석하기
예제를 보면서 해석해보자.
1.
total_votes = primary.pivot_table( values = "votes" , index="state" , columns ="candidate", aggfunc="sum", fill_value=0 )
primary라는 df에, 피컷테이블함수를 적용시키는데,
values = 통계함수를 적용할 df의 특정열
index = 그룹화의 1번째 기준이면서, 피벗테이블(df)의 index로 가져올 primary의 특정열
columns = 그룹화의 2번째 기준이면서, 피벗테이블(df)의 columns로 가져올 primary의 특정열
aggfunc = 2개의 그룹화기준을 가지고 values에 들어간 특정열에 적용시킬 통계함수를 문자열로 표현
fill_value=0 NaN을 0으로 처리
각 주별, 당선자들의, 득표수, 총합
total_votes
2.
primary.pivot_table(values="fraction_votes", index="state_abbreviation", columns="party", aggfunc="mean")
values = fraction_votes(해당후보의 득표율)을 가지고, 통계함수를 계산할 것인데,
index에는 state의 축약어 / columns에서는 정당 으로 가져오면서, 2개를 기준으로 그룹화 한뒤,
aggfunc를 통해 <해당 후보의 득표율의> mean(평균값)을 구한다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
24. 실전 데이터분석(1~6) - pivot_table / groupby (0) | 2018.03.12 |
---|---|
참고 : pandas 추가 - 데이터분석3 요약 정리 (0) | 2018.03.11 |
22. pandas 추가 – 데이터 그룹화 함수 이해하기 (0) | 2018.03.05 |
21. pandas 추가 – DataFrame 데이터 변형(중복행 제거/ 매핑/ 치환/ 카테고리 자료형) (1) | 2018.03.03 |
20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack)) (3) | 2018.03.02 |
22. pandas 추가 – 데이터 그룹화 함수 이해하기
Series를 데이터 그룹화(groupby)하기
df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'],
'key2' : ['one', 'two', 'one', 'two', 'one'],
'data1': np.random.randn(5),
'data2': np.random.randn(5)})
위 DataFrame에 key1과 key2에는 중복된 값이 있는 것을 확인할 수 있다.
이 때, key1열이 a인 행들에 대해, data1의 평균을 구하고 싶다고 가정하자.
(이전까지는 key1의 유니크한 값으로 마스크 –> 반복문 (df.loc( 마스크, data1열)) 을 통해 평균을 계산했었다. 하지만, 데이터 그룹화를 통해 더 쉽게 할 수 있다)
데이터 그룹화를 하기 위해서는, Series나 DataFrame에 groupby()함수를 실행할 수 있다.
- 통계량을 계산할 열(Series) . groupby( 기준이 될 열(Series) ) 형식으로 실행하게 되는데,
grouped = df["data1"].groupby( df["key1"])를 통해
key1열을 데이터그룹화를 한 다음, data1열의 통계량을 계산할 준비를 한다.
값을 확인할 수 없는 이유는 , key1열을 기준으로 data1열을 그룹화만 했을 뿐이며, 통계함수는 사용하지 않았기 때문이다.
grouped에는 ket1의 값인 a를 포함하는 행과 b를 포함하는 행을 각각 그룹화하여 동시에 가지고 있다. - 이렇게 그룹화까지만 된 것인 grouped 를 이용해서, 통계함수로 통계량을 계산한다.
grouped.mean()
이러한 groupby()함수의 절차를 [ split – apply – combine ] 이라고 한다.
기준열을 지정하여 특정열을 그룹별로 나누고 – 각 그룹에 통계함수를 적용하고 – 최종적인 통계량이 산출된 것은 통합해서 표시해주기 때문이다.
(my : groupby로 데이터를 그룹화 했으면, 반드시 통계함수를 적용시켜야하는구나. 통계량 구할라고 하는 거구나!)
이제 그룹화의 기준이 될 열을 2개 이상 지정할 수도 있다. 2개 열의 성분이 모두 같은 것만 하나의 그룹이 된다.
이 때, 그룹화의 기준열이 2개이면, 계층적 인덱스로 적용된 Series가 나온다.
- means = df["data1"].groupby( [ df["key1"], df["key2"]]).mean() 를 통해, 기준열2개로 data1열을 그룹화 후 평균까지 구하자.
나온 결과에서, 기준열이 2개면, 계층적 인덱스가 적용된 Series가 나온다.
이 때, unstactk()함수를 적용해서 최하위 인덱스를 칼럼으로 올린 뒤, 데이터를 분석해도 된다.
DataFrame을 데이터그룹화(groupby)하기
지금까지는 데이터 그룹화(groupby)를 적용시키는 것에 특정열 1개를 Series로 주었는데,
DataFrame에 대해서도 groupby()를 적용시킬 수 있다.
Series에서 했던 것과는 방식이 조금 달라진다.
- Series 의 데이터 그룹화 : 특정열인덱싱.groupby ( 기준열인덱싱 )
- DataFrame의 데이터 그룹화 : df . groupby ( “기준이 될 컬럼명”) 를 통해 특정열이 아닌, df의 모든 열에 대해서 통계량이 계산된다.
- df.groupby("key1").mean() 를 적용시켜보자.
- df.groupby("key1").count() 를 통해, 각 key1열의 값을 가지는 행들의 갯수를 세서 산출해줄 수 있다.
- 2개의 열을 기준으로 할 수도 있다. 마찬가지로 인자에, 리스트형식으로 칼럼명을 넣어준다.
DataFrame을 데이터 그룹화 해서, 특정열x 전체 열을 그룹화 하더라도, 특정열에 대한 통계량만 산출할 수 도 있다.
그룹화결과물에, 통계함수를 적용하기 전에 [ “컬럼명”]으로 뽑아내면된다.
반복문을 이용하여, 그룹화 직후의 결과물 확인해보기(통계함수 적용하기전에)
그룹화를 수행한 직후의 결과물을 확인할 수 없었으나, 반복문을 통해 그룹화에 대한 결과물 확인이 가능하다.
key1 이라는 열에 대해, name, group이라 2변수로 매 반복문마다 값을 받아서, 각각의 print해보자.
- for name, group in df.groupby("key1") :
print(name)
print(group)
key1이라는 열의 각 성분 별로 그룹화를 시켰는데,
name이라는 변수에는 각 그룹으로 나뉘는 기준열의 성분이 들어가서 출력되며, group에서는 기준성분을 포함하는 행들을, 모든 열별로 출력이 된다.
만약 기준열이 2개라면, name변수 자리에 소괄호()를 이용해서 2개의 변수에 각 기준열의 성분을 받아야한다.
딕셔너리를 이용해서 그룹화 직후의 결과물 보기
df.groupby( “key1”)의 결과물은 2개의 변수로 받을 수 있었다.
이것을 list()함수를 씌워서, 순서대로 짤라서 넣게 되고, dict()함수를 이용해서 딕셔너리 형태로 담는다.
이 때, 그룹화된 결과물에 list()함수를 입히면, list의 형태가 [ ( 성분1, group1) , (성분2, group2) , … ]형태로 담길 것이다.
이것을 dict()함수를 이용하면 { 성분1 : group1, 성분2 : group2 }의 딕셔너리 형태로 넣을 수 있는 것이다.
(my : 아! name , group을 한번에 딕셔너리에 담을 수 없어서 그룹화결과물 –> list로 쪼개어 담기 – >dictionary에 한꺼번에 담아주기 )
- pieces = dict ( list( df.groupby("key1") ) ) 를 통해, 그룹화결과물을 딕셔너리의 형태로 살펴보자.
{ a라는 키값 : 그룹화된 결과물 } 형태로 나타난다. - 이것을 딕셔너리 열인덱싱(키 인덱싱) 하여 b에 대한 그룹화 결과물만 볼 수 있다.
pieces["b"]
기준열 대신 별로도 정의된 Series나 딕셔너리를 이용하여, 매핑 & 데이터 그룹화
df2 = pd.DataFrame(np.random.randn(5, 5),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])
5 x 5 DataFrame이 있는 상태에서, df2의 컬럼(a,b,c,d,e)를 다른 값으로 매핑하기 위한 딕셔너리를 생성할 것이다.
딕셔너리의 {키값 = 매핑시킬 칼럼들 : 밸류값 = 매핑할 더 작은 종류의 값들} 형태로 만들어야한다.
(21강에서는 특정열의 성분들을 매핑해서, 더 적은 수의 성분들로 치환했었다. 이 때, m to n 딕셔너러+ apply()함수가 이용됬었음
키값 : 특정열의성분들 –> 밸류값 : 매핑할 더 작은 종류의 값들)
map_dict = {'a': 'red', 'b': 'red', 'c': 'blue',
'd': 'blue', 'e': 'red', 'f' : 'orange'}
이제 groupby를 통화 그룹화를 할 때, 기준이 되는 인자에 딕셔너리를 넣고, axis를 1로 주어, 열 방향(→)으로 지정하여
(위에서 특정열에 대한 성분을 기준으로, 그 값을 포함하는 <index(행들)>를 그룹화 했던 것과는 다르게,)
컬럼들을 그룹화한다. 이 때, 딕셔너리의 키값을 가진 컬럼들이, 밸류값들로 더 작게 매핑되서 컬럼들을 그룹화 한다.
(axis 인자를 안 주었을 때는, 기준열의 성분을 포함하는 행들을, 그룹화하였고
딕셔너리로 매핑 + aixs =1을 준 경우에는, 해당 매핑값을 기준으로 해당 값에 속하는 칼럼들을 모아놓는다.
- dict( list( df2.groupby(map_dict, axis=1) ) ) 를 통해,
axis=1으로 컬럼들을 그룹화 할 것이고, 딕셔너리를 통해 매핑하면서 그룹화한다.
그룹화된 결과물을 보기 위해서, list로 쪼개고, 딕셔너리에 담았다. - 그룹화결과물을 바로 보지 말고, 통계함수를 적용해서 합계를 보자.
df2.groupby(map_dict, axis=1).sum()
a부터 e의 열을 red, blue로 매핑한 뒤, 각 red, blue에 해당하는 칼럼들을 그룹화하고 나서, 합을 구한 것이다.
Series를 가지고 매핑 한뒤, 그룹화 할 수 있다. 위에서 사용한 딕셔너리를 이용해서, Series를 만들어서 해보자.
map_s = pd.Series(map_dict)
- df2.groupby(map_s, axis=1).count() 를 통해, 딕셔너리와 마찬가지로, 첫번째 인자에 Series를 넣고 axis=1로 준다.
.count()를 통해 그룹화된 열의 갯수를 확인할 수 있다.
이렇게 그룹화된 데이터에 적용할 수 있는 통계함수는 아래와 같다.
데이터 그룹화 결과물에 통계함수가 아닌, 사용자 정의 함수 적용하기
데이터 그룹화를 통해 얻어진 결과물에, 통계함수 외 사용자 정의함수를 적용할 수 도 있다.
먼저, df의 key1열을 기준으로 데이터그룹화 시켜놓고 변수에 저장한 뒤, 함수를 새로 정의할 것이다.
peak_to_peak()함수를 정의하는데, 받은 array의 < 각 열>마다 최대값 – 최소값을 반환하는 함수다.
grouped = df.groupby("key1")
def peak_to_peak(arr):
return arr.max() - arr.min()
그룹화된 결과물에 agg()함수를 적용하게 되면, 그룹화된 각 그룹마다 사용자 정의함수(각 열의 최소-최대)를 적용할 수 있게 해준다.
데이터 그룹화의 결과물에 .agg()를 이용해서, 일반적인 통계함수도 적용시킬 수 있다.
이 때, 인자에 “문자열”로 해당 함수를 호출하게 된다.
그룹화된 결과물에 대한 전체 통계량 보기
그룹화된 결과물에 .describe()를 통해 전체 통계량을 확인할 수 있다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
참고 : pandas 추가 - 데이터분석3 요약 정리 (0) | 2018.03.11 |
---|---|
23. pandas 추가 – 데이터 분석 by 그룹화 +피벗 테이블(pivot_table) (0) | 2018.03.06 |
21. pandas 추가 – DataFrame 데이터 변형(중복행 제거/ 매핑/ 치환/ 카테고리 자료형) (1) | 2018.03.03 |
20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack)) (3) | 2018.03.02 |
19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating) (1) | 2018.03.01 |
21. pandas 추가 – DataFrame 데이터 변형(중복행 제거/ 매핑/ 치환/ 카테고리 자료형)
중복된 행 제거하기
df = pd.DataFrame({'k1': ['one'] * 3 + ['two'] * 4,
'k2': [1, 1, 2, 3, 3, 4, 4]})
위와 같이 딕셔너리형태의 value값에 [ 성분 ] * n 또는 직접 입력하여, 위<->아래 행이 모두 중복된 성분을 가지는 행이, 여러개 있는 DataFrame이 있다.
- df.duplicated()함수를 이용해, 성분이 중복되는 행이 있을 때, 아래 행에다가 True를 나타내는 Boolean 마스크를 뽑을 수 있다.
이 것을, 마스크로 인덱싱하여 중복된 행을 제거할 수 있지지만 - df.drop_duplicates()를 사용하여, 중복된 행들이 제거되고 unique한 행들만 얻을 수 있다
이 때, 새로운 열(0~6까지를 성분으로 가지는 )을 추가해서, 완전히 중복되는 행이 없도록 만들어보자.
df["v1"] = np.arange(7)
완전히 중복된 행이 없는 상태에서, drop_duplicates( )함수에 인자로서, 중복검사의 기준이 되는 열을 지정할 수 있다.
이 때, 중복된 행을 제거하는 과정에서 아무 인자도 안주면, 중복 되는 것 중 가장 첫번째 껏만 남긴다.
- df.drop_duplicates( ["k1" ] ) 는 k1열만 기준으로 중복된 행을 제거한다.
- df.drop_duplicates( ["k1", "k2"] , keep="last" )을 통해 k1열과 k2열을 기준으로 모두 중복된 행을 제거하는데,
keep인자에 last를 통해, 중복 행 중, 가장 마지막행만 남길 수 있다.
(default 혹은 keep = “first”는 가장 첫 행만 남긴다,
데이터 매핑(Mapping) with 딕셔너리
df의 특정 열이, 유한한 값 중 1가지를 가진 상태에서, 단순화를 위해 더 작은 가짓수의 값들 중 하나로 매핑(mapping)하고 싶을 수 있다.
예를 들어, 특정열이 7가지의 성분을 가지는데, --- > 중간 딕셔너리(7to3)를 만들어 사용자정의함수로 ---> 3가지 성분을 가진 새로운 열로 범위를 좁힐 수 있다.
df2 = pd.DataFrame({'food': ['bacon', 'pulled pork', 'bacon', 'Pastrami',
'corned beef', 'Bacon', 'pastrami', 'honey ham',
'nova lox'],
'ounces': [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})
food라는 열을 더 작은 가지수로 매핑하기 위해 다음과 같은 딕셔너리를 생성한다.
food열의 unique한 성분(소문자)이 키값이 되고 : 더 작은 범위로 매핑시킬 값들이 value가 된다.
meat_to_animal = {
'bacon': 'pig',
'pulled pork': 'pig',
'pastrami': 'cow',
'corned beef': 'cow',
'honey ham': 'pig',
'nova lox': 'salmon'
}
사용자 정의 함수인 .apply( lambda x : ~ )를 이용해서
food열의 값들이 -->meat_to_animal에 –> pig, cow, salmon 중 하나의 값으로 치환시킨 뒤, 새로운 열로 추가해보자.
(13강에서는 apply()함수의 인자에 들어갈 func = lambda x: x.max() – x.min() 으로 미리 함수를 정의해놓고 집어 넣었다.
df. apply ( func , axis = 0 ) 을 통해 df의 각각의 열에 대해, 성분의 최대값-최소값을 계산해서 각 열별로 Seriess형태로 나오게 했음)
df2의 food열에 대해서 .apply()함수를 적용시켜야 한다. 각 성분들이 lambda의 x로 들어갈 것이다.
( 특정 열에 apply( lambda x: )를 적용하면, 열의 각 성분들이 for문 처럼 모든 성분들이 대입되어 돌아간다.)
- df2["animal"] = df2["food"].apply(lambda x: meat_to_animal[x.lower()]) 를 호출하게 되면
(1) df2에는 bacon도 있고, Bacon도 있으니, 먼저 food열의 각 성분을 의미하는 x에 .lower()함수를 통해 다 소문자로 바꾼다.
(2) food열의 각 성분(소문자)을 의미하는 x.lower()를 meat_to_animal의 열인덱싱 시켜,
딕셔너리에 있는 키 값들 중 하나이므로, 해당하는 value값인 pig or cow or salmon 중 하나가 튀어나올 것이다.
(3) 각 food열의 성분들에 대해, 딕셔너리의 열인덱싱을 통해서, 더 작은 범위의 성분들이 각각 나온 것을
새로운 열인 df2[“animal” ] 에 하나씩 담는다.
값 치환하기 with replace()함수 in Series
이전 12강에서 결측값 NaN을 처리하기 위해 다른값으로 치환 한 적이 있다.(df.dropna / df.fillna(value=)/df.isnull() [“전체기준열”]로 마스크생성)
pandas의 Series에서는 .replace()라는 함수를 제공하여, 손쉽게 특정숫자를 치환할 수 있다.
s = pd.Series([1., -999., 2., -999., -1000., 3.])
위 Series에서 몇몇 값들은 많이 벗어난 이상치(outlier)를 확인할 수 있다.
- 이 이상치를 NaN으로 치환해보자.
s2 = s.replace(-999, np.nan) - NaN도 다른 값으로 치환할 수 있다.
s3 = s2.replace(np.nan, 0)
하지만, NaN을 치환할 때는, fillna( 값 ) 을 통해 더 간편하다.
s2.fillna(0)
카테고리 자료형(범주형 데이터) 생성, 추가, 대소관계
지금까지의 dataset에서 유한가지 값들 중 한 가지를 성분으로 하는 특정열을 많이 보았다.
예를 들어, 성별을 나타내는 열에서는 male/female 를 가지는 경우가 대표적이다. 이것을 범주형 or 카테고리형 데이터라고 한다.
pandas에서는 Categories라는 특별한 형태의 자료형을 제공한다. df의 특정열에서 본 것과 유사하게, Series로 제공되는 자료형이다.
단순하게 문자열로만 저장되어있는 자료형을, 카테고리형(범주형)으로 바꾸게 되면, 메모리 양을 획기적으로 줄이면서 동시에 이에 대한 각종 통계 분석도 간편해진다.
df3 = pd.DataFrame({"id":[1,2,3,4,5,6], "raw_grade":['a', 'b', 'b', 'a', 'a', 'e']})
df3 가운데 하나의 열을 카테고리 자료형으로 변환해보자.
raw_grade열은 a,b,e중 하나의 값을 가질 수 있는데, 단순히 문자열로 저장되어있다. 이를 카테고리 자료형으로 치환해보자.
- 카테고리 자료형으로 만들고 싶은 열에 .astype()함수를 적용하고, 그 type을 “category”로 주어 새로운 열로 추가해보자.
df3["grade"] = df3["raw_grade"].astype("category")
열 인덱싱을 통해서, 카테고리 자료형의 Series라는 것을 확인해보자.
a, b, e 3가지 카테고리 자료형을 가질 수 있는 것을 확인할 수 있다. - 카테고리의 값을 확인 하는 방법은, 열인덱싱.cat.categories 를 입력하면 된다.
df3["grade"].cat.categories - 카테고리의 기존값을 다른이름으로 새롭게 정의해 줄 수 있다. cat.categories로 확인된, 각 순서대로 대응되어서 들어간다.
df3["grade"].cat.categories = ["very good", "good", "very bad"]
(my : cat.categories는 카테고리 자료형 특정열을 확인 + 이름바꾸기가 가능하구나.) - 카테고리의 값을 추가해 줄 수 도 있다. cat.set_categories () 함수에 리스트 형식으로 인자를 넣어줘야한다.
이 때, 인자에는 기존 카테고리의 값도 포함시켜서 리스트형식으로 넣어주면, 기존 값은 유지하면서
해당 열을 확인해보면, 카테고리의 값이 늘어나는 것을 확인할 수 있다.
df3["grade"] = df3["grade"].cat.set_categories( ["very bad", "bad", "medium", "good", "very good" ])
( 카테고리열인덱싱.cat.categories 로 확인하는 것이 정상?)
카테고리의 또다른 특징은, cat.set_categories()의 인자에 넣어준 리스트의 순서대로, 대소관계를 가진다는 것이다.
이것은 sort_values(by=”열”)로 확인해보면, 오름차순 정렬 될 때, 1번째 very bad > 4번째 good> 5번째 very good 순서로 정렬이 된다. 이말은 마지막에 넣어준 카테고리값이 가장 크다는 것을 의미한다.
이러한 카테고르의 대소관계의 특징은, 사원 > 대리> 과장> 부장> 사장 같은 자료형에 대해서 효과적으로 정렬할 수 있다.
숫자 데이터의 카테고리화
연령을 나타내는 열이 숫자로 구성되어 있으며, 일정 나이 기준으로 청년, 성인, 노년 처럼 구간을 나눈 뒤, 관리하면 편해진다.
조건문을 활용해서 나눌 수 도 있지만, pandas에는 빠르게 숫자데이터를 카테고리화 하는 기능을 가지고 있다.
2개의 파이썬 리스트에서 ages 는 카테고리화 할 숫자데이터이며, bins는 각 구간을 나눠줄 숫자값이다.
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
bins = [18, 25, 35, 60, 100]
18~25 / 25~35 / 35~60 / 60~100 이렇게 총 4구간이 존재하게 될 것이다.
pandas에서 제공하는 pd.cut( , )함수는 인자로 (카테고리화 할 숫자데이터, 짜를 구간의 구분값)를 넣어 쉽게 카테고리화 할 수 있다.
이렇게 cut(,)함수로 잘린 데이터는 카테고리 자료형 Series로 반환되게 된다.
- cats = pd.cut(ages,bins) 를 통해 자른 뒤, cats라는 곳에 옮겨 담는다.
확인해보면, ages가 5개의 구간 분값에 의해 4구간의 카테고리 자료형으로 반환된다.
cats = pd.cut(ages,bins) - cats.codes 를 통해, ages의 각 성분이 몇번째 구간에 속해있는지 정수index처럼 표시되는 것을 알 수 있다.
cats.codes
예를 들어, 20이라는 것은 0=첫번째 구간에, 27은 1=두번째 구간에 속한다는 것을 알 수 있다. - cats.value_counts() 를 통해서, 값x 각 구간에 따른 성분의 갯수를 확인할 수 있다.
cats.value_counts()
(my : 특정열.value_counts()는 특정열 성분에 따라 포함되는 행수를 파악하는데 쓰였지만,
카테고리 자료형(Series)에서는, 각 구간에 속한 성분의 갯수도 파악할 수 있다)
pd.cut(, ) 함수를 실행할 때, bins에 준 각 구간 구분값이 자동으로 카테고리명( (18,25] )을 결정하는 것을 알 수 있다.
이 때, pd.cut(,)을 호출시, labes = [ 리스트]형식으로 인자를 추가하면 각 카테고리명을 직접 지정해 줄 수 있다.
- 1. bins로 구분값이 5개이므로 –> 4개의 구간이 생긴다. 각 구간에 대한 카테고리명 4개를 담을 리스트를 만들자
group_names = ["Youth", "YoungAdult", "MiddleAged", "Senior"] - 2. pd.cut() 함수를 호출 할 때, 숫자데이터, 구간 구분값 + labes 인자를 추가로 주자.
pd.cut(ages, bins, labels= group_names) - 3. 각 구간별 성분의 갯수를 알고 싶으면 .value_counts()를 확인해야한다.
cats2 = pd.cut(ages, bins, labels= group_names)
각 구간 구분값으로 bins를 정의해서 나누었었는데, 이것이 귀찮다면, pandas에서 알아서 판단해서 데이터의 길이를 잘라주고 구간을 설정해주는 방법도 있다.
data = np.random.rand(20)
data라는 numpy의 1차원 array를 정의해놓은 다음, pandas의 pd.cut()함수를 호출하는데,
2번째 인자에서 각 구간 구분값(bins)이 리스트형식으로 넣어줬던 것을 –>
data 성분 값을 기준으로 자동으로 구간을 나누게 하기 위해서, 나눌 구간의 갯수만 입력해준다.(성분의 최소값 ~ 최대값를 보고 4개구간나눔)
추가로, 구간을 나눌 때 data 성분의 소수점 몇번째까지 고려해서 나눌지를 정하게 해주는 precision = 인자도 추가한다
- pd.cut(data, 4, precision = 2 ) 를 실행하게 되면,
20개의 data성분에 대해, 동일한 길이의 구간으로 4개를 나누었고, 기준은 소수2번째 자리까지를 기준으로 한다.
pd.cut(data, 4, precision = 2 )
한편, pandas에서는 qcut이라는 함수도 제공한다. 지정한 갯수만큼 구간을 정의하는데,
위에 cut()함수는 최대값 쵯소값만 고려해서 구간을 나눈 것에 비해
그 분포가 고려된, 분위 수를 구분값으로 구간을 나누는 함수다.
1000개의 랜덤 1차원 array를 데이터로 해보자. 이 때, 성분인덱싱으로 5개만 보자.
data2 = np.random.randn(1000)
data2[:5]
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
23. pandas 추가 – 데이터 분석 by 그룹화 +피벗 테이블(pivot_table) (0) | 2018.03.06 |
---|---|
22. pandas 추가 – 데이터 그룹화 함수 이해하기 (0) | 2018.03.05 |
20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack)) (3) | 2018.03.02 |
19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating) (1) | 2018.03.01 |
18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium) (0) | 2018.02.28 |
20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack))
계층적 인덱스 - 정렬함수
물류회사에서 고객정보를 관리하는데, 고객의 주소를 index로 하여 데이터를 관리한다고 가정해보자.
가장 계층인 시(군)에서부터 작은 계층인 번지까지 하나씩 내려올 것이다. ex> 서울특별시 강남구 삼성로 212
이렇게 계층적인 정보를 index로 사용하고 싶을 때, 이것을 계층적 인덱싱이라고 한다.
10개 랜덤수를 Series를 만드는데, 인덱스를 2차원 리스트(혹은 2차원 array)로 주면, 층이 2개인 index를 가지게 된다.
s = pd.Series(np.random.randn(10),
index=[["a", "a", "a", "b", "b", "b", "c", "c", "d", "d"],
[1, 2, 3, 1, 2, 3, 1, 2, 2, 3]])
첫번 째 층에는 a부터 d까지, 2번째 층에서는 1~3까지의 정수가 나와있다. 실제로 Series.의 index를 확인해보면 MultiIndex라고 표시되어있다.
이렇게 계층적 인덱스가 있을 때, 인덱싱을 어떻게 수행하는지 알아보자.
가장 바깥층 인덱스부터 인덱싱하는 것이 원칙이다.
- s["b"] 를 통해서, 정수로 성분 n-1번째 인덱싱 으로 쓰이는 s[ ]에, 가장 바깥층을 index명을 적어서, 그 하위계층의 인덱스+성분을 얻게 된다.
(원래는 정수를 넣어서 s[0]을 하면 1번째 성분, s[1]을 하면 2번째 성분이 나오는 Series의 성분인덱싱으로 쓰이는 곳이었다) - 가장 바깥층의 index명으로 범위인덱싱도 할 수 있다.
s["b":"c"] - 인덱스란에 ()소괄호를 넣어, (상위계층,하위계층) 형식으로 넣으면 하위 계층까지 지정해서 인덱싱할 수 있다.
s[ ("b", 3)]
확인 결과, 소괄호 없어도 된다. [ , ]인덱싱처럼 콤마를 주어 하위계층을 입력하면 [첫번째 층, 두번째 층]을 지정해서 인덱싱할 수 있다.
s[ "b", 3]
(원래, []안에 인자가 여러개 들어갔을 때, 같은 위상에 있는 연속적인 값을 입력할 때는 소괄호를 통해 입력한다)
아! 그래서 행 인덱싱은 따로 .loc[]를 이용했구나, 2차원의 경우 열인덱싱에서 계층적인덱싱하라고??
열인덱싱란에 [행,렬]을 적어봤자 행렬이 아니라, 계층적인덱싱이다.
.loc[]의 인덱싱란에 적어야 [행,렬]이 된다. - 마찬가지로, s[첫번째 층 인덱스, 두번째층 인덱스]로 인덱싱하는데, 콜론을 이용하여, 2번째 층의 인덱스가 2인 것만 뽑아낼 수 있다.
s[ : , 2 ]
DataFrame에서도 계층적 인덱싱을 해보자.
Series에서는, index만 2차원 리스트형식으로 줬지만, DataFrame에서는 columns까지 2차원으로 주자.
df = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[["a", "a", "b", "b"],
[1, 2, 1, 2]],
columns=[["Seoul", "Seoul", "Busan"],
["Green", "Red", "Green"]])
- 복수 계층의 인덱스 와 칼럼에 이름을 붙힐 때는, 파이썬의 리스트 형식으로 주면 된다.
df.index.names = [ "key1", "key2"]
df.columns.names = ["city","color"]
DataFrame의 열에 대한 계층적 인덱싱을 해보자.
- DataFrame의 열도 가장 상위계층을 먼저 인덱싱해야한다.
df["Seoul"] - 이제 상위계층의 하위계층까지 지정해서 인덱싱해보자. 열의 최하위계층까지 간다면 하나의 열만 추출하므로 Series형태로 나올 것이다.
인덱스는 그대로 계층적 인덱싱을 하고 있다.
df["Seoul", "Green"]
행의 인덱싱도 기존 인덱싱과 유사하게 최상위계층부터 인덱싱해야한다.
- df.loc["a"] 를 통해 상위계층 a를 인덱스로 가지는 dataFrame과
df.loc["a", 1]를 통해 상위a , 하위 1이라는 인덱스를 가지는 하나의 행-> Series형태로 얻을 수 있다.
이때, Series의 인덱스는 기존 DataFrame의 계층적인 컬럼들이 인덱스로 들어가는 것을 확인할 수 있다.
.loc를 이용해서, 행과 열을 동시에 계층적 인덱싱을 할 수 있다( 다만 각 행, 렬에서 순차적으로)
- 행과 열을 동시에 계층적 인덱싱할 때, 소괄호()를 통해서 상위->하위계층으로 들어갈 수 있다.
df.loc["b", ("Seoul","Red")]
상위 b행 , 상위 Seoul열-> 하위 Red열 인덱싱
df.loc[("b", 2), "Busan"] 를 통해서, index( b->2) / columns Busan만 인덱싱 할 수 있다. - 행과 열을 동시에 인덱싱할 때는, 모든 계층을 다 타고 내려가면, 특정 성분이 나올 것이다.
(행 or 열 하나만 인덱싱하여 최하위까지 가면, Series가 나온다)
df.loc[("b", 1), ("Seoul", "Green")]
계층적 인덱스에도 인덱스를 기준으로 정렬을 할 수 있다.
이전시간에는 df.sort_index() 만 호출하면 인덱스가 자동으로 오름차순으로 정렬이 됬었다.
여기에 level이라는 인자를 주어, 해당하는 인덱스의 층수(정수 인덱스 : 0,1,2,…)를 주면, 그 층의 인덱스를 오름차순으로 정렬할 수 있다.
(sort_index()에 axis인자를 주지 않으면, 열에 따른 행 방향(axis=0) 기준으로 정렬한다. )
- df.sort_index(axis=0, level=1) 를 통해,
행 방향(↓)으로 행index가 + 2번째 층의 인덱스(key2)가 오름차순 되도록 정렬시킨다. - level인자에 정수 인덱스가 번거롭다면, 층의 이름을 명시해서 사용할 수 있다.
- 행에 따른 열 방향(→)으로 columns을 오름차순 정렬하면서, 계층을 명시할 수 있다.
df.sort_index(axis=1, level=0) 를 통해 1번째 계층인 city를 알파벳 순서로 busan->seoul 순으로 오름차순 정렬 된다.
df.sort_index(axis=1, level=1) 를 통해 2번째 계층인 color 오름차순으로 green->red 순으로 정렬된다.
계층적 인덱싱에서, 성분(값)기준 정렬을 할 수 있다.
이전 13강에서 sort_values( by ="칼럼명")를 통해 특정 칼럼의 성분을 기준으로 오름차순으로 정렬했었다.
(sort_values by = 칼럼명 은 하나로 묶여서 생각하자)
여기에 ()소괄호를 이용해서 계층적 인덱싱을 하면 된다.
계층적 인덱스 - 통계함수
계층적 인덱스에 통계함수를 적용해보자.
이전에는 df.sum(axis=0) 을 통해서 각 열들에 대해 행 방향(↓)의 합을 구했다.
여기에 level인자를 추가해서 <어디서 계산을 끊어서 갈지>기준이 되는 index계층을 지정하면 된다.
만약 axis=0이라면, 열에 따라 행방향으로 합을 구하므로, 끊어주는 기준은 행(index)이 된다.
만약 axis=1이라면, 행에 따라 열방향으로 합을 구하므로, 끊어주는 기준은 열(columns)이 된다.
즉, 방향에 따라 <끊어서 계산하는 기준>이 level이다.
axis=0으로서, 각 열들(행 방향)으로 합은 구하기 때문에 ---> level은 열이 아닌 index가 기준이 된다.(어디까지의 합인지 끊어주는 기준)
- df.sum(axis=0, level=0) 을 통해, 각 열들에 대해 행방향으로 합을 각각 구하는데,
어디까지의 합인지 끊어주는 기준은 level=0인 1번째 index 층이 기준이 된다. 즉, a와 b를 구분해서 각 열의 성분들의 합을 구한다. - df.sum(axis=0, level=1) 를 통해, key2라는 열에는 a-1,2/ b-1,2의 계층이 있으나, 그것을 무시하고
key2 index의 1과 2가 끊어주는 기준이 된다( 1번째 계층 a,b는 무시되어 서로 섞인다)
level에 원하는 인덱스 또는 컬럼의 특정계층 이름(name)을 명시해서 통계함수를 적용할 수 있다.(컬럼명 아님!)
만약 axis=0이라면, 열에 따라 행방향으로 합을 구하므로, 끊어주는 기준은 행(index)이 된다.
만약 axis=1이라면, 행에 따라 열방향으로 합을 구하므로, 끊어주는 기준은 열(columns)이 된다.
즉, 방향에 따라 끊어서 계산하는 기준이 level이다.
DataFrame 특정열의 성분을 계층적 인덱스로 변환
기존의 DataFrame의 특정열에 포함된 값을 계층적 인덱스로 변환하거나 혹은 그 반대로 할 수가 있다.
df2 = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),
'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
'd': [0, 1, 2, 0, 1, 2, 3]})
*** range(0부터 총 갯수) / range( 시작수, 포함되지않는 끝수, 폭) : 내림차순으로 하기 위해서는 폭을 -1로 주어야한다!
c와 d열의 성분을 계층적 인덱스로 바꿔보자.
- set_index()함수에 리스트[]형식으로, 계층순서대로 컬럼명을 주면된다.
(my : 인덱싱할 때는 [ ] 안에 소괄호로 계층을 주고 / 각종 함수에서는 ( )안에 리스트로 계층을 주네?!)
df3 = df2.set_index( ["c", "d"] )
그럼 c의 성분들에 대해 d의 성분들이 알아서 박힌다. 그리고 c, d열은 사라진다 - c열과 d열을 계층적 인덱스로 주면서, 기존의 c열과 d열을 유지하면서 c,d열을 인덱스로 내려주고 싶다면
set_index()함수의 인자에, 열->index 으로 내려가는 drop을 False로 주면 된다.
df2.set_index(["c","d"], drop=False)
현재 존재하는 계층적 인덱스를-> 열로 올리면서 동시에 기본 정수를 index로 주는 함수는 reset_index()함수다.
DataFrame 인덱스와 칼럼 전환
DataFrame 모양을 변형하기 위해 아래 예시 데이터를 보자.
df4 = pd.DataFrame(np.arange(6).reshape((2, 3)),
index=['Seoul', 'Busan'],
columns=['one', 'two', 'three'])
df4.index.name = "city"
df4.columns.name = "number"
계층적 인덱스는 아니다. 이 때, stack()함수를 사용하면, DataFrame의 최하층 컬럼자체가 –> 인덱스의 최하위 index 층으로 붙게 되면서, Series가 된다. 여기서는 컬럼이 단일층이므로, 해당 칼럼이 넘어가버려 Series가 되는 것이다.
- df4.stack() 를 통해 단일컬럼(number)가 단일 인덱스(city)의 하위계층으로 붙게 되는 것을 확인할 수 있다.
기존의 칼럼이 사라지면서, Series의 형태가 되어버린다.
*** 주의할 점은, stack()함수와 set_index()의 작동방식이 다른 것이다.
(1) set_index( [ “컬럼1”, “컬럼2” ])는 컬럼이 내려와서 기존의 인덱스를 <대체> 해버린다.
drop=False로, 칼럼1,2를 살릴 수 도 있다.
(2) stack()함수는 컬럼의 최하위계층이 기존 인덱스의 <최하위계층으로 내러와서 붙는다> - df5.unstack()을 통해서, 인덱스의 최하위 계층(number)를 –> 칼럼의 최하위계층으로 올린다
만일 계층적 인덱스 중 특정계층의 index를 columns으로 올리고 싶다면 level인자로 지정해준다.
- df5.unstack(level=0)를 통해, 첫번째 계층의 index를 --> 컬럼으로 올린다.
이전에는 df5.unstack()만하여 최하위 계층의 index가 --> 컬럼으로 올라갔었다. - level인자를 특정 인덱스명으로 지정해줘도 똑같이 컬럼으로 특정index층이 올라가는 것을 확인할 수 있다.
이제 2개의 Series를 만들고, concat()함수를 통해, s1밑에다가 s2를 행으로서, 단순연결하여 s3의 Series를 만드는데
keys인자를 통해 s1과 s2의 각각 index이름이 one과 two로 구분된다.
기존에 인덱스가 있는 series들의 합이므로, keys로 준 인덱스명이 상위계층의 인덱스가 되어버림?!!
(axis=1로서 행방향으로 열을 각각 붙혔다면, 칼럼명이 생성되는데 여기서는 index가 있는 상태에서 또 인덱스명으로 들어가므로, 계층적인덱스가 되어버린다)
(concat()함수는 axis인자가 default일 때, 열에 따른 행 방향으로 붙힘, 즉 밑에다가 행으로 붙힌다.)
s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
s3 = pd.concat([s1, s2], keys=["one", "two"])
이제 좀 더 복잡한 DataFrame을 stack과 unstack()함수를 살펴보자.
기존 계층적 인덱스를 가지고 있는 Series df5를 2개로 나누어 df를 만들고, 거기다가 name이라는 컬럼 전체이름에, 각각 left, right라는 컬럼명도 지정해주었다.
df6 = pd.DataFrame({"left": df5, "right": df5 + 5},
columns=["left", "right"])
df6.columns.name = "side"
- df6.unstack()을 통해 최하위 index인 number 라는 인덱스명이 –> 칼럼의 최하위층로 올라간다.
- unstack()을 할 때, level인자를 주어서, 첫번째 계층(city)을 최하위 칼럼으로 올릴 수 도 있다.
df6.unstack(level=0) - stack과 unstack을 동시에 할 수 도 있다.
df6.unstack(level="city").stack(level="side") 을 통해,
city 인덱스는 unstack하여 칼럼으로 올리고, 칼럼명 side는 stack()을 통해 index 최하위계층으로 내려보자.
데이터 분석에 있어서, stack/unstack 등의 계층적 인덱싱을 하면 복잡해지므로, 사실상 비추하는 분야이다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
22. pandas 추가 – 데이터 그룹화 함수 이해하기 (0) | 2018.03.05 |
---|---|
21. pandas 추가 – DataFrame 데이터 변형(중복행 제거/ 매핑/ 치환/ 카테고리 자료형) (1) | 2018.03.03 |
19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating) (1) | 2018.03.01 |
18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium) (0) | 2018.02.28 |
참고 : matplotlib 분석 정리 (0) | 2018.02.28 |
19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating)
여러개의 파일을 DataFrame으로 받아들인 뒤, 서로다른 DataFrame을 하나로 합치는 방법은 2가지가 있다.
1. 공통된 하나의 열(또는 행)을 기준으로, 동일한 값을 가지는 행을 각 DataFrame에서 찾은 뒤
n개 X m개 조합으로, 행을 모두 가지도록 합치는 경우 : merging(병합)
2. 동일한 index나 columns을 가지고 있는 경우, 연속적으로 붙히기 : concatenating(연결)
1. Merging(병합)
필요한 패키지들을 import 한 뒤, 2개의 DataFrame을 생성하자
df1에는 key열에 중복된 값이 들어가있고, df2에는 중복된 값이 없다는 것이 차이점이다.
df1 = pd.DataFrame({"key": list("bbacaab"),
"data1": range(7)})
df2 = pd.DataFrame({"key": list("abd"),
"data2": range(3)})
- 2개의 DataFrame은 key라는 공통적인 열을 가지고 있다. 그리고 a, b, c, d 중 하나의 값을 가지게 된다.
pandas에서 제공하는 pd.merge(df1, df2 , on="key") 함수를 실행해보자. on인자에는 공통적인 열명을 준다.
예를 들어, df1에서 key열의 값이 b인 행을 보자. index 0, 1, 6행 3개가 있다. df2에서는 index1인 행 1개가 있다.
3 – 1의 조합을 보면 총 3 X 1의 3가지 경우가 존재한다. 이에 해당하는 조합들 3개 모두를 merge된 df에는 가진다.
또 예를 들면, key열의 a라는 값을 가지는 df1행은 index 2, 4, 5행이고 df2는 index 0행이다. 3 X 1해서 3개의 조합을 모두 가진다.
나머지 key열의 값(c, d)들은 공통적인 값이 없기 때문에 생략된다.
pd.merge(df1, df2, on="key") - 이제 추가적으로 how=”outer”라는 인자를 주어서, key열의 값 중 한쪽 DataFrame에만 있는 행들도 추가해준다. 없는 쪽의 값은 NaN으로 표시된다. 즉, key의값이 동일한 행들은 n X m조합 + 값이 동일한 행이 없는 경우는 NaN을 포함해서 추가
pd.merge(df1, df2, on="key" , how = "outer") - 다음으로 인자를 how=”left” 로 주어보자. 그러면 왼쪽에 있는 df1이 기준이 되어 고정되고, df2는 동일한 키값을 가질 때마다, 여러번 달라 붙혀지게 된다. 만약 달라붙는 df2에 해당 key값이 없는 경우는 NaN으로 붙혀진다.
pd.merge(df1, df2, on="key", how="left")
마찬가지로 how=”rightt”를 주게 되면, df2는 고정된 상태에서, df1이 동일한 key값을 가지고 있을 때마다 달라 붙으며, 없는 경우 NaN으로 표시된다.
이번에는 df1, df2 둘다 key열에 중복된 값을 가지는 데이터를 이용해보자.
df3 = pd.DataFrame({"key": list("bbacaab"),
"data1": range(7)})
df4 = pd.DataFrame({"key": list("ababd"),
"data2": range(5)})
- how인자에 inner를 넣으면, 넣지 않은 것과 동일하다. 공통된 값이 없으면 생략된다.
pd.merge(df3, df4, on="key", how="inner")
이제 각 df에 중복된 값이 존재하므로, 예를 들어 b를 보면, df3에는 3개, df4에는 2개의 행이 b를 포함하고 있다.
그러므로 b에 대해서 3 x 2의 조합이 존재하게 된다. a의 경우도 2 x 2 로 총 4개의행을 가진다.
마찬가지로 outer를 인자로 주면, 공통되지 않은 값들(c, d)도 NaN을 달고 추가된다.
한편, 기준이 되는 열의 칼럼명이 key로 같지 않더라도, 각 df에서 기준의 열을 인자로 지정하여, merging을 수행할 수 도 있다.
df5 = pd.DataFrame({"lkey": list("bbacaab"),
"data1": range(7)})
df6 = pd.DataFrame({"rkey": list("abd"),
"data2": range(3)})
- pd.merge(df5, df6, left_on="lkey", right_on="rkey")를 수행하게 되면, 왼쪽df5의 lkey와 오른쪽 df6의 rkey를 기준으로 값이 같은 것들끼리 m x n 조합으로 나타나게 된다. 예를 들어, b의 경우 df5는 3개, df6은 1개로 총 3개의 행이 나타나는 것을 확인할 수 있다.
이제, index를 기준으로 merging을 해보자.
먼저, 각각의 DataFrame의 열과 index를 merging해보자. key의 값들과 index의 값이 같을 때 가능하다.
이 때, index를 기준으로 하는 df의 인자는 right_index=True를 줘서 index를 기준으로 설정할 수 있다.
left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'], 'value': range(6)})
right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])
- pd.merge(left1, right1, left_on="key", right_index=True)
왼쪽인자에 들어가는 것은 _on=”열명”을 통해 열을 기준으로하고, 오른쪽인자는 _index=True를 통해 index명을 기준으로 놓으면 된다.
left1의 key열에는 a가 3개, right1의 index에는 1개가 있으니, 3 x 1 개의 행이 나오는 것을 확인할 수 있다.
이제 DataFrame 둘다 index를 기준으로 merging을 해보자.
두 DataFrame의 index에 merging할 수 있는 값들이 명시되어 있는 것을 알 수 있다.
(my : index기준으로 merging하면 공통된 index에 대해서 열들을 합칠 수 있다.how=outer를 주면 모든 행들을 합칠 수 있다. )
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
index=['a', 'c', 'e'], columns=['Seoul', 'Incheon'])
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
index=['b', 'c', 'd', 'e'], columns=['Daegu', 'Ulsan'])
- pd.merge(left2, right2, how="outer", left_index=True, right_index=True)
두 df 모두, index를 기준으로 merging하도록 옵션을 준다. 그리고 how=”outer”를 넣어서 공통된 값이 없는 행들도 표시되도록 해보자.
Concatenating(연결)
concatenating은 두 DataFrame을 행 방향(default로 할 시) or 열 방향으로 단순 연결하는 것을 의미한다.
pd.merge()는 2개의 데이터를 콤마를 이용해서 넣어줬지만, pd.concat()는 리스트형식으로 연결한다.
예시를 위해 3개의 Series를 만들자.
s1 = pd.Series([0, 1], index=["a", "b"])
s2 = pd.Series([2, 3, 4], index=["c", "d", "e"])
s3 = pd.Series([5, 6], index=["f", "g"])
- pandas에서 제공하는concat()함수를 이용해서 리스트형식으로 넣어보자.
pd.concat([s1, s2, s3]) 를 호출하면, 단순히 index가 증가하는 형태로 연결되어 새로운 Series가 생성된다. - 만약, axis=1을 인자로 주면, Series가 아닌, DataFrame의 형태로 각각 열 방향으로 연결된다.
이 때, 3개의 각 Series는 ab/ced/fg로 공유하는 index가 없었기 때문에, 칼럼들은 default하게 0부터 시작해서 총 3개의 열이 생성된다.
pd.concat([s1, s2, s3] , axis= 1)
이번에는 s1과 index는 같으면서 성분*5 + s3를 행 방향으로 단순연결한 새로운 Series s4를 생성해서 concat()해보자
s4 = pd.concat([s1 * 5, s3])
- s1과, s1의 인덱스와 일부가 같은 s4를 열 방향(axis=1)으로 concat()해보자.
pd.concat([s1, s4], axis=1) 의 경우, 동일한 index에 대해서는 첫번째 인자s1에다가 s4를 붙히는 것이다.
index가 같은 a와 b부분에서는 자연스럽게 열방향으로 연결된다. - 여러개의 Series를 concat()로 연결할 때, DataFrame에다가 컬럼명을 명시해줄 수 있다.
어차피 3개의 Series를 axis=1 로 concatenating한다면 3개의 열이 생길 것이다.
각 열의 컬럼명을 keys=라는 인자로 지정해 줄 수 있다.
pd.concat( [s1, s2, s3] , axis= 1, keys=["one", "two", "threes"] )
DataFrame에 대해서도 동일한 방법으로 concatenating할 수 있다.
df1 = pd.DataFrame(np.arange(6).reshape(3, 2),
index=['a', 'b', 'c'], columns=['one', 'two'])
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2),
index=['a', 'c'], columns=['three', 'four'])
이 때, .reshape( 3 , 2) 함수를 이용해서, 0부터 5까지의 1차원 array를 3행x2열로 만들 수 있다.
concat()함수를 호출할 때, ignore_index = 인자를 True로 넣어서, 기존index를 신경쓰지 않고 연결해보자.
df3 = pd.DataFrame(np.random.randn(3, 4), columns=['a', 'b', 'c', 'd'])
df4 = pd.DataFrame(np.random.randn(2, 3), columns=['b', 'd', 'a'])
- pd.concat([df3, df4], ignore_index=True) 를 통해, axis=1이 없으므로, 열 방향(행 으로 추가)으로 연결하면서
기존 인덱스가 겹치는 0, 1을 무시하고, df3에 df4를 행으로 추가한다.
만약 ignore_index 옵션이 없다면, 단순 연결의 index가 0, 1, 2, 0, 1 로 인덱스명 그대로 행에 추가된다.
그러므로, 어떤 DataFrame을 행으로 단순 연결시킬 때는, ignore_index를 붙혀서 index를 첨부터 새로 붙게하여 자연스럽게 붙게하자.
pd.concat([df3, df4], ignore_index=True)
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
21. pandas 추가 – DataFrame 데이터 변형(중복행 제거/ 매핑/ 치환/ 카테고리 자료형) (1) | 2018.03.03 |
---|---|
20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack)) (3) | 2018.03.02 |
18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium) (0) | 2018.02.28 |
참고 : matplotlib 분석 정리 (0) | 2018.02.28 |
17. Matplotlib – 데이터 분석하기(3개) (4) | 2018.02.28 |
18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium)
1. Seaborn
matplotlib을 기반으로 만들어진 시각화 라이브러리로 사용방법이 아주 유사하다.
공식사이트 갤러리로 가서 살펴보면 된다.
(https://seaborn.pydata.org/examples/index.html)
2. Bokeh
웹 브라우저 상에서의 시각화에 효과적인, Python 인터랙티브 시각화 라이브러리로,
플롯을 html 파일로 export하여, 웹브라우저 상에서 확인할 수 있고, interactive하게 조작이 가능하다.
예를 들어, 선택한 부분만 히스토그램이 활성화되는 기능도 있다.
마찬가지로 matplotlib과 사용방식이 유사하다. 공식사이트의 갤러리에서 살펴보자.
(https://bokeh.pydata.org/en/latest/)
3. Folium
지리적 데이터 시각화에 특화된 라이브러리로, 자바스크립트 라이브러리인 leaflet.js기반이다.
지도 데이터 사용을 위해 선행되어야 하는 적으며, 설치와 사용법이 간단하다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
20. pandas 추가 – 계층적 인덱싱(정렬함수, 통계함수적용, 인덱스와 칼럼 전환(stack,unstack)) (3) | 2018.03.02 |
---|---|
19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating) (1) | 2018.03.01 |
참고 : matplotlib 분석 정리 (0) | 2018.02.28 |
17. Matplotlib – 데이터 분석하기(3개) (4) | 2018.02.28 |
16. Matplotlib - figure, subplot-axes/ Plot모양 변형/ 눈금,레이블,범례 / 축의 범위 확인 및 변경 (1) | 2018.02.27 |
참고 : matplotlib 분석 정리
1. 전체기준열의 각 values값이 포함된 행수의 세기(분포) => 라인플롯 그리기
파읽 읽기 –> shape / head() / columns(칼럼명들만 리스트로 반환) 보기 –> 전체 기준열을 인덱싱한 뒤, .value_counts()로 포함된 행수를 Series로 받기 –> 그 Series의 분포에, .sort_index()를 통해 오름차순 정리하기 –> 새로운 변수에 담아서, 변수.plot()그리기 –> plot그릴 때 반환 받은 axes변수로 xticks(yticks) 정수로 바꿔주기 –> xlim(ylim)으로 범위 늘려주기
2. index를 name열로 바꾼 뒤, 각 name행에 따른 조건을 만족하는 특정열 1,2를 뽑고-> 특정열 1,2의 비율 비교하기
name열을 데이터프레임.set_index( [칼럼명] )을 통해 새로운index 만들기 –> 특정열1과 특정열2를 인덱싱하여 합이 10000이 넘도록 조건문을 달아 새로운 boolean마스크 변수 생성-> 마스크변수를 행인덱스에 집어넣고, 뽑아낼 특정열1,2를 열인덱싱에 넣어, 해당조건을 만족하는 행 뽑아낸 뒤 새로운 DataFrame변수에 집어넣기 –> 새로운 DataFrame으로 plot()을 stacked옵션을 주어서 만들기 –> 각 비율을 계산한 열을 추가로 생성하여, 비율 열1,2로 효과적인 비율 비교의 bar플롯 그리기
3. 여러개의 특정열의 values를 Series의 index로 만들고, 그 각각의 values가 여러개의 특정열에 포함된 행의 수를 뽑아내서 히스토그램 만들기
여러개의 특정열을 관리하기 쉽게 칼럼명만 칼럼인덱싱으로 뽑아내서 변수에 담기 –> 여러개의 특정열에 나타는 values중에 NaN이 포함된 것을 none문자열으로 치환한 뒤, np.unique()로 중복없는 values리스트들을 새로운 변수에 담기-> 중복없는 values리스트를 index로 준 빈 Series 만들기 –> for문을 통해, 여러개의 특정열 칼럼명 변수를 통해 하나씩 순회하면서, 각 열에 value_counts()를 통해 각 value가 포함된 행수를 빈 Series에 .add()하기 + fill_value = 0 으로 특정열 속의 NaN은 0으로 계산해서 더하기 –> 히스토그램 그리기
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
19. pandas 추가 – 데이터 합치기 2가지 방식(merging, concatenating) (1) | 2018.03.01 |
---|---|
18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium) (0) | 2018.02.28 |
17. Matplotlib – 데이터 분석하기(3개) (4) | 2018.02.28 |
16. Matplotlib - figure, subplot-axes/ Plot모양 변형/ 눈금,레이블,범례 / 축의 범위 확인 및 변경 (1) | 2018.02.27 |
15. Matplotlib 사용하기 –라인 플롯, 바 플롯, 히스토그램, 산점도 (1) | 2018.02.25 |
17. Matplotlib – 데이터 분석하기(3개)
필요한 패키지 import
%matplotlib nbagg
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
csv 파일 읽어오기 by Pandas
battles = pd.read_csv( "data/game-of-thrones/battles.csv", sep=",")
death = pd.read_csv( "data/game-of-thrones/character-deaths.csv", sep=",")
battles라는 DataFrame 에는 전투 데이터 관련이,
deaths 에는 모든 등장인물의 등장정보와 사망년도/사망장소에 관련 정보가 있다.
데이터 확인하기
battles.shape
battles.head()
battles.columns
* 데이터.columns를 하면 컬럼명들이 리스트형식으로 반환된다.
데이터 분석하기(1)
1. 전체기준열의 각 values값이 포함된 행수의 세기(분포) => 라인플롯 그리기
파읽 읽기 –> shape / head() / columns(칼럼명들만 리스트로 반환) 보기 –> 전체 기준열을 인덱싱한 뒤, .value_counts()로 포함된 행수를 Series로 받기 –> 그 Series의 분포에, .sort_index()를 통해 오름차순 정리하기 –> 새로운 변수에 담아서, 변수.plot()그리기 –> plot그릴 때 반환 받은 axes변수로 xticks(yticks) 정수로 바꿔주기 –> xlim(ylim)으로 범위 늘려주기
첫 번째로, deaths라는 데이터프레임에서 죽은 책의 이름을 의미하는 [ 죽은책 번호(Book of Death)에 따른 –> 죽은사람의 명수(행의 수=사람의 수)를 ] 시각화해보자. 즉, 전체기준열의 각 values값이 포함된 행수의 분포를 보면 된다.
- 각 행이 한 사람을 의미하므로, value_counts()로 세어진 행수 = 사람의 수를 의미하게 된다.
- book_nums_to_death_count = deaths["Book of Death"].value_counts().sort_index() 를 통해
기준열(책 번호-Book of Death)에 따른 죽은 인물들의 명수를 구하여 –> book_nums_to_death_count 변수에 담는다.
(13강. sort_index()는 열인덱싱을 통해 시리즈로 나타날 데이터의 index를 오름차순으로 만들어준다)
1편에서는 49명이 죽었고, 2편에서는 73명이 죽었고… 를 한눈에 알 수 있다.
라인플롯 그리고 보기 편하게 변형하기
- fig없이 바로 데이터.plot()함수를 이용해, 해당 데이터로 라인플롯을 그린다.
color, marker, linestyle을 인자로 함께 주면서, 데이터.plot()으로 그린 좌표평면을 반환 받는다.
ax1 = book_nums_to_death_count.plot( color ="k", marker = "o", linestyle = "—") - 1.0부터 5.0로 소수점까지 포함된 xtick을, 정수로 바꿔주기 위해서 좌표평면.set_xticks()함수에 np.arange(1,6)을 이용한다
ax1.set_xticks(np.arange(1,6)) - x축의 범위를 조금 늘려 0부터 6까지 나타내기 위해서
ax1.set_xlim( [0,6] ) - y축은 정수니까, 범위만 조금 늘려준다.
데이터를 시각화해서 보니, 3권에서 가장 많은 사람이 죽었다는 것을 알 수 있다.
데이터 분석(2)
2. index를 name열로 바꾼 뒤, 각 name행에 따른 조건을 만족하는 특정열 1,2를 뽑고-> 특정열 1,2의 비율 비교하기
name열을 데이터프레임.set_index( [칼럼명] )을 통해 새로운index 만들기 –> 특정열1과 특정열2를 인덱싱하여 합이 10000이 넘도록 조건문을 달아 새로운 boolean마스크 변수 생성-> 마스크변수를 행인덱스에 집어넣고, 뽑아낼 특정열1,2를 열인덱싱에 넣어, 해당조건을 만족하는 행 뽑아낸 뒤 새로운 DataFrame변수에 집어넣기 –> 새로운 DataFrame으로 plot()을 stacked옵션을 주어서 만들기 –> 각 비율을 계산한 열을 추가로 생성하여, 비율 열1,2로 효과적인 비율 비교의 bar플롯 그리기
battles에 대한 데이터를 다시 한번 확인하기 위해 .head()를 확인한다.
battles 데이터에서는 전투군과 수비군에 대한 정보가 서술되어있다.
공격군과 수비군의 수(attacker_size, defender_size)의 합이 10,000이상되는 대규모 전투를 뽑은 뒤,
대규모 전투 별로 병력차이를 비교해보고 Boxplot(수평barh+stacked)으로 시각화 해보자.
- 단순히 0부터 정수로 되어있는 index를 전투의 이름(name)으로 바꾼 뒤, battels에 대입하여 덮어씌우자.
battles = battles.set_index(["name"])
battles.head() - 공격군과 수비군의 합이 10000만 이상 되는 것을 뽑기 위해, 해당조건으로 boolean마스크를 생성한 뒤 변수에 담아준다.
large_battles_mask = battles["attacker_size"] + battles["defender_size"] > 10000 - 마스크로 행인덱싱 하고, 확보할 열 2개(공격군, 수비군 수)을 열인덱싱하여, 조건에 맞는 행만 뽑아낸 뒤, 새로운 데이터프레임에 담는다.
large_battles = battles.loc[large_battles_mask, ["attacker_size", "defender_size"]]
large_battles.head() - 이제 대규모전투만 뽑은 DataFrame에 대해, 각 군의 상대적인 비율을 확인하기위해 bar플롯(수평barh + stacked)을 그려보자.
large_battles.plot(kind="barh", stacked=True , fontsize = 8)
공격군+수비군의 합이 만명이 넘어가는 대규모 전투는 총 10개가 있었고,
bar플롯(stacked)을 통해 병력차이를 확인할 수 있다. - 공격군과 수비군의 효과적인 비율비교를 위해서, 비율을 먼저 계산하여 새로운 열로 추가하고, bar플롯으로 그려보자.
비율로 계산하면,, 수평bar+stacked로 바플롯을 그리면, 수평막대를 완전히 채울 수 있다.(2개의 합 = 1)
large_battles["attacker_pcts"] = large_battles["attacker_size"] / ( large_battles["attacker_size"] + large_battles["defender_size"] )
새로운 열은 공격군 / (공격군 + 수비군의 합) 으로 계산된 공격군의 비율이다. 수비군의 비율도 유사하게 추가해준다.
large_battles["defender_pcts"] = large_battles["defender_size"] / ( large_battles["attacker_size"] + large_battles["defender_size"] ) - 각 비율 열 2개만 인덱싱하여 barplot을 그려보자.
ax3 = large_battles[ ["attacker_pcts" , "defender_pcts"] ].plot( kind = "barh", stacked = True, fontsize = 8)
데이터 분석(3)
3. 여러개의 특정열의 values를 Series의 index로 만들고, 그 각각의 values가 여러개의 특정열에 포함된 행의 수를 뽑아내서 히스토그램 만들기
여러개의 특정열을 관리하기 쉽게 칼럼명만 칼럼인덱싱으로 뽑아내서 변수에 담기 –> 여러개의 특정열에 나타는 values중에 NaN이 포함된 것을 none문자열으로 치환한 뒤, np.unique()로 중복없는 values리스트들을 새로운 변수에 담기-> 중복없는 values리스트를 index로 준 빈 Series 만들기 –> for문을 통해, 여러개의 특정열 칼럼명 변수를 통해 하나씩 순회하면서, 각 열에 value_counts()를 통해 각 value가 포함된 행수를 빈 Series에 .add()하기 + fill_value = 0 으로 특정열 속의 NaN은 0으로 계산해서 더하기 –> 히스토그램 그리기
- 컬럼 중에 전투에 참여한 가문들이 나와있는 <컬럼명>만 발췌해보자. ( .columns –>칼럼명 리스트 / .columns[ a:b] –> 일부 칼럼명 리스트)
*** 칼럼명들만 모아서 가지고 열인덱싱[칼럼명들 변수]로 손쉽게 인덱싱할 수 있다. 원래는 필요한 열들만 발췌해야하나, NaN이 껴있기때문에, 필요한 열들의 칼럼명을 뽑아서 다른 처리를 먼저 해줘야하기 때문이다.
이 때, 컬럼들이 붙어 있으므로 <컬럼명>열 번호로 인덱싱한 뒤, 컬럼 명 변수에 담아서 확인해보자.(5번째 열부터 12번째 열까지 총 8개)
(cf. battles[4:12]면 열 인덱싱이고 , battles.columns[4:12]는 컬럼명만 가져온다.
col_names = battles.columns[4:12]
col_names - 참여가문들이 나와있는 8개 열에 대한 중복없는 <unique한 가문의 이름=value>들만 뽑아야한다. 하지만 데이터를 보면, NaN 값이 있다.
NaN을 포함하고 있을 때에는 unique()로 뽑을 때 오류가 나므로, 먼저 NaN부터 다른 문자열(“none”)으로 채운 뒤, none을 제거하는 방식으로 가야한다.
열인덱싱 할 때, 뽑아놓은 컬럼명을 대입하여, 그것만 열인덱싱한 상태에서, NaN값을 “None”으로 채운 뒤, value(값)들만 새로운 변수에 담자. 그러면 8개열의 모든 value들(중복포함)이 각 열별로 NaN없이 담길 것이다.
(여러개의 열인덱싱.values를 하면, 각 열별로 values들이 담긴다)
열별로, 각 values들이 담긴 것을, [:5]를 통해, 5열만 살펴보자.
house_names = battles[col_names].fillna("None").values
house_names[:5]
여러개의 열들을 인덱싱한 뒤, .vaules로 값들을 넣으면 각 열에 대한 values들이 2차원 array로 얻어지는 것을 확인할 수 있다.
각 열에 대한 values들만 각 열별로 나누어 담기 때문에, index같은 것은 없다. - numpy에서 제공하는 unique()함수를 적용해서, 다시 담아넣자.
np.unique()함수를 쓰면, 각 열별로 value값들이 2차원 array형태를 가졌는데, 1xn의 1차원 array로 중복없이 싹다 모아버린다.
house_names = np.unique(house_names)
house_names - 간편하게 none을 제거하기 위해서, 열인덱싱(1차원에서는 n번째 성분을 의미!)으로 boolean마스크를 이용해서,
성분들 중에 None이 아닌 것만 담도록 인덱싱해서 다시 담아준다. 즉, 성분(각 열로 취급)들 중에 None이 아닌 모든 것들을 인덱싱하는 것이다.
house_names = house_names[house_names != "None"]
house_names
NaN이 제거된 순수한 가문들(8개열의 values)들을 얻을 수 있다.
해석하자면, 최소 1회 전투에 참여한 모든 가문들의 이름을 모은 것이다. - 이제 위의 가문들을 index로 하고, 성분은 0으로 하는 Series를 하나 정의하자.
최종 목표인 <가문을 index로 하여, 각 가문에 따른 전투 참여 횟수>의 Series를 만들기 위한 준비이다.
( data에 0만 넣으면 모든 index에 대해서 0으로 찍히는 Series가 만들어진다)
house_to_battle_counts = pd.Series(0, index = house_names)
house_to_battle_counts
여기에, 각 열을 기준으로 index에 합산되도록 해서, 전투 참여횟수를 구할 수 있다. - 만들어진 가문이름을 index로 한 Series에다가,
battles의 열인 attacker_1부터 4까지, defender_1부터 4까지 순회하면서 등장하는 가문이름이 포함된 행의 수를 .value_counts()를 통해 Series형태로 얻은 다음 대입해준다. 이렇게 Series로 계산하는 이유는, 열을 인덱싱해서 얻어지는 Series의 경우 .value_counts()를 하게 되면, 각 가문을 의미하는 value값을 가지는 행들의 갯수가 [values값(가문이름) – 해당 횟수]의 Series형태로 뽑히기 때문이다.
그래서 만들어둔 빈 Series인 house_to_battles_counts에 .add( Series형태 ) 하게 되면, 더해질 value값과 가문이름이 동일한 곳에 해당 횟수가 더해지진다.
순회는 for문을 통해서 이미 만들어둔, col_names를 가지고 하나씩 대입해서 순회한다.
이 때, Series.add ( Series )의 인자에 추가로, fill_value=0를 넣어 순회하는 8개의 특정열에 포함된 NaN은 0으로 치환해서 add해주게 한다.
참고) battles["attacker_1"].value_counts()
for col in col_names :
house_to_battle_counts = house_to_battle_counts.add( battles[col].value_counts() , fill_value = 0)
총 8개의 열에서, 각 가문이 몇번 등장했는지, 데이터가 나오게 된다.
해석해보자면, Lannister가문과 Stark가문이 다른가문에 비해 압도적으로 많이 전투에 참여했다는 것을 알 수 있다. - 이제 이 데이터로 히스토그램을 그려보자.
ax4 = house_to_battle_counts.hist(bins=10)
해석)
대체로 1회~ 3회 정도까지 전투에 개입한 가문이 제일 많고 , 15~18회 전투를 참여한 가문은 1번으로 드물다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
18. Matplotlib - 기타 데이터 시각화 라이브러리(Seaborn, Bokeh, Folium) (0) | 2018.02.28 |
---|---|
참고 : matplotlib 분석 정리 (0) | 2018.02.28 |
16. Matplotlib - figure, subplot-axes/ Plot모양 변형/ 눈금,레이블,범례 / 축의 범위 확인 및 변경 (1) | 2018.02.27 |
15. Matplotlib 사용하기 –라인 플롯, 바 플롯, 히스토그램, 산점도 (1) | 2018.02.25 |
참고 : pandas 분석 정리 (0) | 2018.02.25 |
16. Matplotlib - figure, subplot-axes/ Plot모양 변형/ 눈금,레이블,범례 / 축의 범위 확인 및 변경
Matplotlib에서는 figure라는 그림단위를 사용하여, 이 안에서 한개 혹은 여러개의 plot을 그리고 관리하도록 지원을 한다.
이 때, figure안에 들어가는 plot하나를 subplot이라 부른다.
figure에 알아보기 앞서 필요한 패키지들과 매직명령어를 import하자
%matplotlib nbagg
import matplotlib.pyplot as plt
import matplotlib
figure –> subplot 으로 세분화해서 그릴 준비하기
- pyplot에서 제공하는 .figure()함수를 호출해서, 내부가 텅빈 초기 figure를 만들 수 있다.
- 이제 생성한 피규어 fig 에 subplot을 추가해야한다. add_subplot( , , )을 호출하여 좌표평면을 나타내는 변수(axes)에 받아줘야한다.
앞에 2개인자는 fig 내에 몇행x몇렬로 subplot을 가질 것인지 지정한다.
마지막 인자는, 행렬의 몇번째 성분에 그릴 것인지를 지정하는 것이다. 2x2라면 1,2/3,4 시작위치를 지정할 수 있다.
ax1 = fig.add_subplot(2,2,1) 라는 명령어를 입력하면, 만들었던 피규어의 2x2 중 1번째 위치에서 빈 좌표평면(ax1)이 그려지는 것을 확인할 수 있다.
마찬가지로 2, 3번째 위치에서 시작하는 subplot의 좌표평면도 나타내자
이렇게 인터렉티브하게 조작할 수 있는 것은 매직명령어로 옵션을 줘놨기 때문에 가능하다.
numpy, pandas로 데이터 생성 후 figure의 subplot마다 그려보기
패키지를 먼저 import한다.
import numpy as np
import pandas as pd
- plt.figure()로 생성한 fig에 .add_subplot()를 통해 생성한 좌표평면 ax1, ax2, ax3가 있다.
pyplot에서 제공하는 plt.plot()함수의 인자에, 1차원 랜덤데이터 50개의 누적합 데이터를 주고, 좌표평면(ax1, ax2, ax3)을 지정하지 않고 그려보자.
아래와 같이, 맨 마지막에 위치한 좌표평면(ax3)에 plot이 그려진다.
만약, 또 좌표평면을 지정하지 않고 그린다면, 그 전인 2번째 좌표평면(ax2)에, 그리고 그다음에는 1번재 좌표평면(ax1)에 그려지게 된다.
즉, 좌표평면을 지정하지 않고 figure의 subplot에 그림을 그리면, 마지막부터 역순으로 plot이 채워진다.
* 해당 subplot에 그림을 그릴려면, subplot생성할 때 마다 지정해준 좌표평면을 선택해줘야하구나! (plt.plot() (x)- > ax.plot() ) - axes를 지정해줘서 plot을 그려주자. 첫번 째로, ax1에다가 히스토그램(x값만 있으면 그려지는) 을 한번 그려보자.
ax1.hist( np.random.randn(100), bins = 20) - 랜덤정수100개가 속하는 구간을 20개의 구간으로로 나타낸다.
아래와 같이 첫번째 좌표평면에 그려지게 된다.(fig-subplot-ax가 없었을 땐, Series.hist(bins=,normed=) ) - ax2 좌표평면에다가는 산점도를 그려보자(산점도는 독립변수 2개가 필요함)
비교하는 독립변수에는 0부터 30전까지 1차원array와 동일한array+3*30개 랜덤수 데이터를 대입하자.
ax2.scatter(np.arange(30), np.arange(30) + 3*np.random.rand(30))
(fig-subplot-ax 없었을 땐, plt.scatter(열인덱싱, 열인덱싱) )
특정axes를 나타내는 변수에 .plot(), .hist(), .scatter( ) 함수를 사용해서 fig > subplot > axes좌표평면에다가 그릴 수 있었다.
figure와 subplot을 좀 더 직관적으로 그리기
pyplot에서 제공하는 plt.subplots (m ,n ) or (m) 을 통해서 fig와 axes를 2개 동시에 반환 받을 수 있다.
fig, axes = plt.subplots(2,3) 를 통해, 2x3 subplot들을 한꺼번에 만들고, fig와 axes도 반환받자.
반환받은 axes에는 2x3짜리 array가 들어가 있으며, 각 성분은 그 위치의 axes에 해당한다. ax1, ax2방식이 아니라, 인덱스로 지정해준 다음 .plot()을 통해 해당 위치에 plot을 그릴 수 있다.
plot 모양 변형하기
라인플롯의 경우, 색/마킹기호/라인스타일 등을 지정할 수 있다. plt.plot()으로 라인플롯을 그리면서,
인자로 color, marker, linestyle이라는 값을 줘보자.
- plt.plot(np.random.randn(30), color="g", marker="o", linestyle="—")
- 인자를 “” 문자열 안에 한번에 줄 수 도 있다.
plt.plot(np.random.randn(30), "k.-") : 검은색, 점모양, 일반라인 - 각종 인자color, marker, linestyle값 목록
라인플롯 이외에, 바 플롯, 히스토그램, 산점도 에는 color과 alpha값을 지정할 수 있다. 그리고 좌표평면axes도 ax 인자로 지정해주자.
subplots를 생성해서 그리면서 채워보자. alpha는 투명도를 의미한다.
fig, axes = plt.subplots(2,1)
- 먼저, 데이터를 만들자, pd.씨리즈인데, 16개 랜덤수이며, index는 list함수로 문자열 16개를 쪼개서 대입한다.
data = pd.Series(np.random.randn(16), index = list("abcdefghijklmnop"))
그리고 이 dataFrame 혹은 Series를 plot으로 그릴 때는, data.plot( )의 인자에, 플롯의종류kind 이외에 ax=라는 인자를 주어,
subplot만들 때 반환받은 axes array를 인덱싱해주면된다. ( 걍 바로 그릴 때는 plt.plot(data직접 생성)으로 그렸었음 )
여기서는 2 x 1 axes가 생성되었으므로, 그것의 첫번째 성분인 axes[0]을 ax=인자에 명시해주자.
* 2x1행렬에서는,,, axes[0]이 1열이 아니라 1행의 성분이 되는구나… axes[1]은 2번째 열이 아니라 2행의 성분.
* 만약, 1x2 행렬이라면 , axes[0]은 1열, axes[1]는 2열이 된다.
*** series 뿐만 아니라, 1x n / n x 1 도 1차원이다!!!!!!! ==> array[n] 열인덱싱이 ~번째 성분만 의미하게 된다.
이어서 그릴axes뿐만 아니라, color와 alpha인자도 주자(라인플롯은 color, marker, linestyle)
data.plot(kind="bar", ax=axes[0], color="k", alpha=0.7) - 마찬가지로, 수평 바플롯을 그리고 색과 투명도를 주자.
data.plot(kind="barh", ax=axes[1], color="g", alpha=0.3)
마찬가지로, 히스토그램이나 산점도도 마찬가지로 입력하면된다.
눈금 및 눈금의 레이블 조작하기
눈금, 레이블, 범례를 조작하기 위해서는 먼저 figure를 만들어야한다.
fig = plt.figure()
만든 fig에 대해서 subplot을 만든다. 간단하게 1x1 서브플롯의 1번째 axes를 지정해주자.
ax3 = fig.add_subplot(1,1,1)
여기에 랜덤한 수 1000개들의 누적합으로, 라인플롯을 하나 그려주자.
ax3.plot(np.random.randn(1000).cumsum())
수평축은 0부터 1000까지 나타나있으며, 기본눈금이 200단위로 되어있는 1000개 랜덤수의 누적합을 라인플롯으로 확인할 수 있다.
수학적으로 이러한 결과물을 random walk라고 부른다.
그리고 이렇게 200단위로 나타나는 x축의 눈금을 matplotlib에서는 x tick , y축의 눈금을 y tick이라 부른다.
- x축의 눈금(tick)을 변형하기 위해서는 해당 좌표평면.set_xticks( [0, 250, 500, 750, 1000] ) 식으로 인자에 리스트형식으로 넣어준다.
ax3.set_xticks( [0, 250, 500, 750, 1000] ) - 이제 x축의 눈금을 –> label(문자열)로 바꿔보자.
원래 x의 눈금(ticks)가 5개 였으므로, tick의 수와 동일한 갯수로 좌표평면.set_xticklabels( [ "문자열1”, "문자열2” , ,,, , "문자열5” ] 로 바꿔주면된다.
이 때, 추가적으로 회전도와 폰트사이즈 등을 줄 수도 있다.
ax3.set_xticklabels( ["one", "two", "three", "four", "five"],
rotation = 20, fontsize = "small") - y축 눈금도 마찬가지로 set_yticklabes ()를 이용하면된다.
다음으로, 현재 띄워놓은 axes의 제목을 변경해보자.
각각 축의 눈금(tick)의 이름(label)도 넣어줄 수 있다.
범례 조작하기
하나의 axes(좌표평면)안에 여러개의 plot들이 들어갔을 때는, 범례가 필요하다. 이를 legend라 한다.
먼저 새로운 figure와 subplot-axes를 만든 뒤 , 3개의 plot을 입혀보자.
fig = plt.figure()
ax = fig.add_subplot(1,1 ,1) : 1행1열의 subplot 1번째 좌표평면ax
라인플롯을 3개 추가하는데, 인자에 바로 데이터를 만들어서 주고, color/marker/linestyle
+ 범례에 들어갈 각 lineplot의 제목을 의미하는label 인자를 다르게 주자.
(아까는 하나의 좌표평면을 지정해서, axes.set_title로 좌표평면의 label을 )
ax.plot(np.random.randn(1000).cumsum(), 'k', label = "one" )
ax.plot(np.random.randn(1000).cumsum(), 'b--', label = "two" )
ax.plot(np.random.randn(1000).cumsum(), 'r.', label = "three" )
.plot()안에 들어가는 label 인자는 범례에서 나타나는 이름이다!
label이라는 인자로 입력된 값은, axes상에 범례를 표시할 때, 각 플롯의 이름이 된다.
- 범례를 표시하려면, 좌표평면.legend()함수를 사용해야하며, 인자로는 loc=”best”를 주면, 최적의 위치에 범폐를 표시한다.
ax.legend(loc="best")
loc의 인자에 따라 범례 위치를 조작할 수 있으나 생략한다.
수직, 수평 축의 범위 확인
현재 표시된 좌표평면 ax.에 get_xlim()이라는 함수를 실행하게 되면, x축 눈금의 범위가 표시된다.
범위를 변경하고 싶다면 set_xlim()함수를 사용한 뒤, 인자로는 리스트형태로 범위를 시작,끝 을 넣어준다.
ax.set_xlim( [ 100,900 ])
수칙축(y)도 마찬가지다.
ax.set_ylim( [-100,100])
다 외울 수 없으니, api나 구글링 하면서 사용하는 것이 좋다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
참고 : matplotlib 분석 정리 (0) | 2018.02.28 |
---|---|
17. Matplotlib – 데이터 분석하기(3개) (4) | 2018.02.28 |
15. Matplotlib 사용하기 –라인 플롯, 바 플롯, 히스토그램, 산점도 (1) | 2018.02.25 |
참고 : pandas 분석 정리 (0) | 2018.02.25 |
14. pandas 데이터분석 해보기(파일읽기, 분석 3가지(고난도), 파일쓰기) (4) | 2018.02.25 |
15. Matplotlib 사용하기 –라인 플롯, 바 플롯, 히스토그램, 산점도
Matplotlib 라이브러리 사용하기
Series나 DataFrame을 plot을 만들 수 있다. 가장 많이 사용하는 것이 라인 플롯, 바 플롯, 히스토그램, 산점도 가 있다.
사용하기 앞서 %매직명령어를 사용하여 플롯 조작활성화에 대한 옵션을 주어야한다.
옵션 인자로서는 nbagg를 입력하면 plot들을 조작할 수 있는 인터렉티브한 상태가 된다. inline으로 주게 되면, 셀에서 생성된 plot에 대해 조작할 수 없다.
두번 째로, 필요한 패키지(라이브러리)들을 impot해야한다. matplotblit은 총 2개의 패키지를 importing한다(2번째 패키지는 산점도에 사용된다)
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
사용할 데이터를 생성한다. 여기서는 random한 수로 이루어진 Series에 인덱스를 주어 생성한다.
s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))
이 시리즈(nx1)의 인덱스는 np에서 제공하는 리스트의 성분이 랜덤한 수 10개를 , index가 0부터 100보다 작은 수이면서 간격이 10이므로, 0 , 10, 20부터 90까지이다.
(이 때, cumsum은 cumulative sum 누적합계의 뜻으로,각 성분에 대해 0,1,2,3,4,5,6,7,8,9가 있다면 0, 0+1, 0+1+2, 순으로 10개가 출력되어,
각 성분을 더해가는 series를 뱉어놓는다. 만약, 2d array라면 설정한 축(cumsum(0)) = 열에 따라 계산되어 [1,2,3],[4,5,6],[7,8,9]였으면 [1,2,3], [1+4, 2+5, 3+6], [1+4+7 , , ] 이러한 방식으로 계산되어 열의 같은위치의 성분끼리 점점 누적되면서 더해져서 나온다.)
라인 플롯 사용하기
라인 플롯 : 독립변수x가 변함에 따라 종속변수y가 어떻게 변화하는지를 나타낸다.
특히 라인플롯은, [연속적인 x]에 따른 y의 변화를 살펴보는데 효과적이다.( 0, 10, 20, … , 90 : 인덱스로 주었다.)
앞에서 만든 series 변수 s를 이용해서 .plot()을 이용해 라인플롯을 그려보자.
series s의 인덱스가 x축 / 각 성분이 y축을 구성한다. 2번째 import한 pyplot의 명령어 plt.plot (s)를 이용해도 똑같은 라인 플롯을 얻을 수 있다.
다음 plot를 생성할 때는, 그전에 만든 plot을 파란색 전원버튼으로 꺼야한다.- 라인플롯을 부분 별로 살펴보자.
x축이 인덱스/y축은 성분값을 나타낸다.우측 상단의 파란색전원버튼을 누르기전까지는 마음대로 조절할 수 있다. 그리고 다음 플롯을 생성할 때는 꺼야한다.
4번째 사각형을 클릭한 뒤, plot에 범위를 지정하면 확대할 수 있따.
디스켓을 누르면 그림형태로 저장할 수 있다.
이제 DataFrame을 만들고, 라인플롯을 생성해보자.
- 랜덤한 10행 4열 array를 만들고, 누적합계를 열로 주면서, 칼럼과 인덱스를 주자.
이 때, 10x4 array에 .cumsum(axis=0)을 줘서 열=행방향↓으로는 누적합계가 되도록 한다. Series일때는 축을 안정해줘도 된다.1차원..
df = pd.DataFrame(np.random.randn(10, 4).cumsum(axis=0),
columns=["A", "B", "C", "D"],
index=np.arange(0, 100, 10))
DataFrame의 인덱스가 x축을, 각 성분들이 y축을 담당하는 것은 Series와 같으나, 칼럼(열)들이 4개의 라인플롯을 만들어냈다.
즉, 칼럼수(열 수) = 라인 플롯의 갯수다. - 만약 여러개의 열을 가진 df에서 특정열에 대한 1개의 라인플롯을 그리고 싶다면, 원하는 열을 Series의 형태로 뽑아지는 열 인덱싱하여서 .plot()을 호출하면된다.
df["B"].plot()
바 플롯(Bar plot) 사용하기
라인플롯은, [연속적인 x]에 따른 y의 변화를 살펴보는데 효과적이다.( 0, 10, 20, … , 90 이 인덱스였다.)
바 플롯은 [숫자상으로 연속적이지 않은 x]에 따른 y의 변화를 살펴본다.
Series데이터를 사용해서 바플롯을 사용해보자.
- 먼저 데이터를 만드는데, 시리즈의 인덱스에 들어간 list( “ 연속된 문자열 “)함수는 연속되어 붙어 있는 문자열들을 하나씩 분리해서 리스트로 만들어준다. 그 리스트 성분들이 index로 들어가 a,b,c,d,..,p까지 되는 것이다. Series에 들어가는 1차원 array가 16개 이므로, 문자열도 연속해서 16개를 적었다.
s2 = pd.Series(np.random.rand(16), index=list("abcdefghijklmnop")) - 바 플롯은 라인 플롯과 동일 함수 .plot()을 호출하는데, 인자에 kind=”bar”를 주어 종류를 바꿔주면 된다.
s2.plot(kind="bar") - 바 플롯을 수평방향으로 줘보자. kind에 “barh”를 주면 된다.
s2.plot(kind="barh")
이제 DataFrame을 만들고, bar plot을 만들어보자.
df2 = pd.DataFrame(np.random.rand(6, 4),
index=["one", "two", "three", "four", "five", "six"],
columns=pd.Index(["A", "B", "C", "D"], name="Genus"))
랜덤한 6x4행렬의 DataFrame을 만들고, 인덱스와 칼럼명을 입력하자.
이 때, 칼럼명에는 coloumns = pd.Index()함수를 이용해 –> 인자로서 각 칼럼명과 함께 name(칼럼이름만)을 입력하는 것을 가능하게 했다.
- df2.bar( kind = “bar”)를 호출해보자. x축에는 인덱스가 있고, y축에는 값이 있고, 칼럼(열) 수 = bar plot의 수가 된다.
- DataFrame의 수평 바 플롯(kind=”barh”)를 호출하면서 추가로, 인자에 stacked=True 로 주게 되면,
인덱스(x축)에 대한 바 플롯이 여러개 나타나는 것이 아니라
[한 인덱스에 모든 바 플롯이 한줄로 나타나게 되어 –> 하나의 index에 대한 각 플롯들(열들)의 성분 비율 ]을 확인할 수 있게 된다.
히스토그램 사용하기
히스토그램의 경우, 라인 플롯, 바 플롯과 다르게, 하나의 변수x만을 가지고 그릴 수 있다. 그 x축-> x가 속하는 구간과 y축->구간에 속한 x의 갯수를 나타내는 것이다. 이 x가 가질 수 있는 값의 구간(bin)의 갯수를 인자로 지정한 뒤, 그 각각의 구간에 속하는 갯수를 막대형태로 그려보자.
히스토그램을 그릴 경우, index가 따로 필요하지 않으며 값들만으로 Series로 구성한다.
히스토그램의 x축에는 bin이라고 하는 구간이, y축에는 그 구간(bin)에 해당하는 x의 갯수가 찍힌다.
s3 = pd.Series(np.random.normal(0, 1, size=200))
인덱스 없는 시리즈를 만들 때, numpy에서 제공하는 정규분포를 의미하는 normal()함수를 사용하였다. 이 normal()함수는 평균과 표준편차를 지정하고, 거기서 추출한 샘플 갯수를 지정해서 뽑아낼 수 있다. 그 결과 평균이 0, 표준편차가 1인 200개의 정규분포를 만들어 낼 수 있다.
만약 2차원으로 만들고 싶다면 size=(200,1) 형식으로 주면 된다.
- 만든 Series를 이용해서 히스토그램을 만들기 위해서는 s3.hist()를 호출하면 된다.
이 때, x축을 matplotlib에서는 bin이라 부르며 인자로 줄 수 있다. 기본적으로 10칸의 bin을 히스토그램이 차지하고 있다.
그래프를 보면 -0.5bin에 해당하는 값의 갯수는 50개가 약간 안되는 것을 알 수 있다. - bin의 갯수를 s3.hist(bins=50) 처럼 bins=구간의 갯수로 직접 지정해줄 수 있다.=> 갯수가 많아지면, 그만큼x가 들어있는 구간도 좁아지면서, 더 세밀하게 관찰 할 수 있다. 히스토그램의 막대 하나하나의 폭이 좋아진 것을 확인할 수 있다.
- 또, bins의 갯수뿐만 아니라, normed인자 = True로 주면, 이전까진 구간에 속한 x의 개수였지만, <각 bin에 속하는 갯수를 전체개수로 나눈 비율, 즉, 정규화된 값>을 bar의 높이로 사용하게 된다. 애초에 200개 샘플을 정규분포에 추출하였기 때문에, 정규화된 결과는 정규분포를 의미하는 종모양과 유사하게 나타난다.
s3.hist(bins=100, normed=True)
산점도(시각적인 상관관계) 사용하기
라인 플롯이나 바 플롯의 목적은 x에 대한 y의 변화를 나타내는 것이 목적이 었다면,
산점도는 서로다른 2개의 독립변수 x1 과 x2와의 관계를 알아볼 때 사용하는 플롯이다.
x1의 값과 x2의 값을 각각의 x/y축으로 하여 2차원 평면상에 점 형태로 나타내는 것이 산점도이다.
데이터로는 np에서 제공하는 지정된 평균/표준편차의 정규분포에서 각각 100개를 2차원형태인 100x1로 추출하여 x1, x2의 array를 만들고,
두 독립변수(100x1, 100x1)를 np에서 제공하는 concatenate()함수를 두 독립변수를 열 방향(axis=1)으로 붙혀서 연결한다
(100x 1 , 100x1 ) = (100 x 2)
이 때, 각 array를 만들 때, Series=1차원이 size=100 이 아니라 (100,1)의 2차원 array로 만들어서-> concatenate()로 연결 할 수 있게 한다.
이제, 각 정규분포에서 뽑아낸 100x1 array 2개를 concatenate()로 붙힌,
100x2 array를 DataFrame으로 만들고, 각 컬럼명을 x1, x2로 준다.
- 이제 df3라는 DataFrame 의 두 칼럼 x1과 x2의 시각적인 상관관계(산점도)를 알아보기 위해, pyplot에서 제공하는 plt.scatter()함수를 호출해야한다. 이 때, 인자로 DataFrame의 각 열을 인덱싱해서 넣어주면 된다.
plt.scatter( df3["x1"], df3["x2"])
산점도 상의 각 점들은, DataFrame의 각 행에 있는 x1, x2성분의 상관관계를 의미한다. 즉, x축이 x1 y축이 x2의 값이다.
만약 x1과 x2의 상관관계가 양의 상관관계면 / 방향으로, 음의 상관관계면 \ 방향으로 점들이 나타날 것이다.
위의 그림은 큰 상관관계가 없다고 볼 수 있다.
'빅데이터 관련 프로그래밍 > Python - bigdata(pandas 기초)' 카테고리의 다른 글
17. Matplotlib – 데이터 분석하기(3개) (4) | 2018.02.28 |
---|---|
16. Matplotlib - figure, subplot-axes/ Plot모양 변형/ 눈금,레이블,범례 / 축의 범위 확인 및 변경 (1) | 2018.02.27 |
참고 : pandas 분석 정리 (0) | 2018.02.25 |
14. pandas 데이터분석 해보기(파일읽기, 분석 3가지(고난도), 파일쓰기) (4) | 2018.02.25 |
13.Pandas DataFrame 데이터분석용 함수(통계함수, 정렬함수, 유용한 함수, 사용자정의함수 적용) (2) | 2018.02.24 |