빅데이터 관련 프로그래밍/Python - bigdata(pandas 기초)

groupby() )
(1) 기준열로 그룹화한 결과물로 - > 그룹화기준 딕셔너리를 만들어 -> 키값대입으로 1개의 그룹화결과물을 구하고
(2) 그룹화결과물마다, 통계함수가 아닌 , 따로 적용할 어떠한 def 함수가 존재할 때,
(3) 그룹화결과물 .apply(def함수) 로 계산한다.

pivot_table ) 
(1) 추출열 / index로 가져올 기준열1/ columns로 가져올 기준열2/ 통계함수 가 명확하고 한방에 그룹화할 때

데이터분석 해석)
(1) 각 ~별 => 기준열
(2) ~의 합/평균/비중 계산하기  => 기준열
(2) A+B열 중 A만 or B만 언급 => 기준열로 간주하고, 나중에 열인덱싱 ( ex> 남아의 출생횟수 ==> 성별을 기준열로 하고 남자열만 인덱싱 )
(3) 전체A 중 B를 가진 => A열을 기준열로 간주하고, 나중에 B열 인덱싱
(4) 특정 ~를 가진 => 그룹화후 인덱싱 & 만약 행인덱싱이 필요하다면 sum()함수의 총합이 달라지므로, 미리 비중계산후 행인덱싱

상위 TOP100 / 상위 ~% 구하기 )
(1) groupby()로 그룹화기준 딕셔너리를 통한 1개 그룹화결과물로 착안하여 , sort_values로 내림차순 + iloc로 정수 행인덱싱 [ :100]의 사용자함수 정의
(2) groupby().apply( 사용자정의함수 ) 적용 : 강제로 그룹화시켰야만, 상위100개가 노출됨
(3) pivot_table로 손쉽게 그룹화시켜서 각 그룹당 상위100 확인



필요 라이브러리 import

%matplotlib nbagg
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt


파일 읽기 + 헤더를 새로운 것으로 지정해주기(기존 csv데이터와 다른이름의 헤더 주기)

pd.read_csv를 이용하여, (notebook작성위치 기준)파일을 불러오면서, 헤더옵션을 주어 새로운 헤더를 지정해주자.(갯수는 동일하게)

names = pd.read_csv("data/us-baby-names/NationalNames.csv", sep="," ,
                     header=0, names=["id", "name", "year", "sex", "births"])

image


  • 데이터 확인하기 ( head()와, shape 관찰하기)


데이터 분석1 – 각 연도별, 성별(남/여)의 총 출생횟수 계산하기 –> 시각화  by 피벗테이블

각 연도별-> 기준열1 “year”
성별(남/여)의 –> 기준열2 “sex”
총 출생횟수 –> 추출열 + sum()통계함수 “births” / “sum

  • groupby()를 이용한다면, names.groupby( [ 기준열1, 기준열2 ] ) [ 추출열 ] .sum()이겠지만
    피벗테이블을 이용해서 데이터그룹화해보자.
    totol_births = names.pivot_table(values="births", index="year", columns="sex", aggfunc=sum)
    totol_births.head()
    image


  • line plot을 그려보자.( index-> x축 / values의 범위 –> y축 /  columns –> 선의 갯수 = 범례의 갯수)
    ax = total_births.plot()
    image
  • plot의 제목을 붙혀주자.
    ax.set_title("Total births by year and sex")
    image



데이터 분석2 – 각 (연도, 성별 기준의) 그룹내에서,
                  각 이름(행)의 출생횟수가 전체에서 차지하는 비중(%)을 나타내는 열 추가하기 by groubpy()함수

피벗테이블을 사용하지 않고, groupby()함수를 사용해서 계산을 해보자.

앞서 22강에서는, 기준열에 대한 데이터그룹화 직후의 결과물( 추출열 적용x, 통계함수적용x)을 보기 위해서는
데이터그룹화 결과물에 list()를 입혀서  [ (기준열성분a, 기준열에a를 가진 dataFrame ) ,  ( b : DataFrameB) , ( c : dfC )] 형태로 나누어지고,
그 list에다가 dict()를 입혀서 { a : a포함 df , b : b포함 df, c: c포함df }의 딕셔너리 형태로 가져와서 볼 수 있었다.
그리고 그 딕셔너리에 [키인덱싱]을 통해서, 해당 키성분을 가진 df를 추출할 수 있었다.


여기서는 2개의 기준열을 가지고 groupby하여 딕셔너리 형태로 뽑아보자. 키가 2개가 될 것이고, 키 2개 성분을 가지는 df가 밸류가 될 것이다.

  • year와 sex열을 기준열로 하여,  groupby함수의 그룹화결과물을 딕셔너리형태로 담아보자.
    이 때, 딕셔너리이므로, head()가 아니라 keys()를 통해,   기준열2개-> 2개의 key ( year, sex)형태의 키들을 살펴볼 수 있다.
    grouped_names_dict = dict(  list(  names.groupby( ["year", "sex"])  )  )
    grouped_names_dict.keys()

    image

  • 기준열 2개로 groupby()한 그룹화결과물을 ---> 딕셔너리로 저장했다면, 보고싶은 2개의 키를
    딕셔너리에 [키 인덱싱]하는데, (key1, key2 ) 순으로 넣어주면 해당 dataFrame을 볼 수 있다.
    예를 들어, 2011년도 남자(M)아이에 해당하는 names데이터프레임만 보고 싶다면,
    해당 딕셔너리의 (2011, ‘M’) 키를 가진 –> 밸류(df)만 가져오면 된다.
    추출열 없이, 전체 names중에 기준을 가지는 행들의 모든 열이 나타나기 때문에,  아이의 name과 출생횟수도 바로 볼 수 있다.

    grouped_sample = grouped_names_dict[ (2011, "M" )]
    grouped_sample.head(10)
    image
    my) dataFrame을 groupby()를 통해서, 딕셔너리형태로 그룹화결과물만 가지고 있다면,
    names라는 df 중, 딕셔너리에서 키값만 인덱싱하면, 원하는 기준으로 자료를 뽑아낼 수 있다.
    굳이 통계함수를 적용안시켜도되며 & 기준열중 원하는 값만을 가지는 것을 바로 뽑아낼 수 있다.
    추출열 없이, 전체 names중에 기준을 가지는 행들의 모든 열이 나타나기 때문에, 
    다른 열의 정보인 아이의 name과 출생횟수도 바로 볼 수 있다.
    (나는 이 딕셔너리를 키값인덱싱으로 개별 결과물을 알 수 있는 그룹화 기준 딕셔너리 라고 부를 것이다.
    이 그룹화 기준 딕셔너리에 키값으로 인덱싱하여 –> 각각의 그룹화된 df을 얻을 것이다)

    참고로,, 그룹화기준 딕셔너리 생성 –> 키값인덱싱으로 그룹화된 결과물 1개를 살펴보기 위함 일뿐?
    결과물 1개를 봐야지.. 집계함수를 정의할 수 있어서?

  • 이제 1개의 그룹화결과물 df에 대해 출생비율을 계산하여 새로운열로 추가해줄 사용자 정의 함수를 정의한다.
    기준열에 2개에 대한 기준값을 –> 그룹화 기준 딕셔너리에 키값 2개( year, ‘M’ or ‘F’) 를 인덱싱하여하여 얻을 수 있는 DataFrame을 인자로 받아
    해당 추출된 df의 births열을 따로 빼낸 뒤 –>
    [ births열 변수  / births열 변수 .sum()총합 ]의 비율계산하여 –>
    새로운 “prop”열을 df에 추가하는 함수다.

    def add_prop(agg_df):
         agg_births = agg_df["births"]
         agg_df["prop"] = agg_births / agg_births.sum()
         return agg_df
    image


  • 이제 사용자 정의 집계함수를 적용시키려면, 그룹화기준 딕셔너리에 키값을 입력해서 얻는 1개의 그룹화 결과물이 아니라,
    groubpy로 그룹화시킨 것에 추출열이 없는 전체 dataFrame에 .apply()를 이용해서 사용자 정의함수를 적용한다.

    names_with_prop = names.groupby( ["year","sex"]).apply(add_prop)
    names_with_prop.head()
    image
    pivot_table기준열을 index / columns로 가져와서 보기가 편해던 반면에,
    groupby는 기본적으로, 열로 유지된 상태의 데이터라 보기 쉽지 않다.
    하지만, [ 그룹화기준딕셔너리로 1개의 결과물 본 뒤-> 각 그룹화 결과물을 전체로 한 비율을 계산해줄 함수정의 –> 함수 적용으로 새로운 열로 추가 ]가 가능하다.
    ( my) 각 그룹화결과물을 전체로 하였으니,, 각 비율은 그룹화했을 때, 합이 1이다. 전체데이터에서 prop열을 보면, 합이 1이 아니고, 섞여있다)


데이터 분석3 – 각 연도, 성별 그룹내에서,  출생횟수(births) 기준 TOP1000 이름 추출하기 by groupby()

상위 TOP 1000 등을 계산하려면, 데이터분석2에서 계산한 prop(비율)열을 먼저 계산되어 있어야한다.?????
위에서 return받은 데이터 names_with_prop을 이용하자.
image

  • 각 그룹화결과물을 전체로한 비율을 열로 추가한 데이터 names_with_prop을 가지고, 또 [ 그룹화기준 딕셔너리 ]를 만들자.
    < 결국에는 groupby로 그룹화한 데이터를 다룰 것이다. 그 전에  딕[키값인데싱]으로 1개의 결과물을 미리 볼 수 있다 >
    grouped_names_with_prop_dict = dict( list( names_with_prop.groupby(["year", "sex"]) ) )

    하나의 샘플을 미리 보자.
    grouped_sample = grouped_names_with_prop_dict[(2011, "M")]
    grouped_sample.head()
    image


  • 이제 우리는, 그룹화기준 딕셔너리를 통해 1개의 그룹 결과물을 보고서 –> 각 그룹 별 상위 1000개의 데이터를 추출해야한다.
    1개의 그룹화 결과물에 대해서births열로 내림차순으로 정렬하고,  .iloc[ :10]을 통해,  10번째 행까지만 뽑아보자!

    grouped_sample.sort_values(by="births", ascending = False).iloc[:10]
    image
    (기존의 데이터가 원래 내림차순으로 정렬되어있던 데이터라서,, 동일한 것처럼 보인다)

  • 위의 1개 결과물에 대한 것에 착안을 해서 ---> 그룹화기준딕셔너리를 만들 때 쓴, 전체 데이터 names_with_prop에 대해서
    1개의 결과물에 적용한 과정을 그대로의 수행하는 사용자 정의함수를 정의하자.
    (1) births열 내림차순정렬 + 1000번째 행까지만 인덱싱하여—> top1000_df 변수에 저장
    (2) top1000_df변수를 return함

    def get_top1000(agg_df) :
         top1000_df = agg_df.sort_values(by="births", ascending = False ).iloc[:1000]
         return top1000_df
    image


  • 정의한 함수를 groupby()한 그룹화결과물에 (통계함수대신) .apply()로 적용한다.
    각 연도, 성별을 기준으로 그룹화된 각 DataFrame에서 births 상위 1000명개씩 끊어서 name행들이 추출될 것이다.

    top1000_names = names_with_prop.groupby(["year", "sex"]).apply(get_top1000)
    top1000_names.head()
    image
    다 보이진 않지만, 1880년도, Female 아이들 중 상위1000개 나타나고 그다음, Male의 상위1000개만 나타날 것이다.


  • 확인을 위해, 전체 데이터인 names_with_prop 의 행인덱싱.loc[]의  조건으로 ( year열 == 2011) & ( sex열 == “M” )을 주어,
    2011년에 태어난 M인 아이들의 수
    shape를 통해 총 몇 행이 있는지 확인하고
    각 그룹결과물 별 top1000데이터 top1000_names의 행인덱싱에 조건을 주어 shape를 확인해보자.
    (.loc[ 인덱스명 ,  칼럼명 ]으로 인덱싱하는것이 보통이나, 행인덱싱란에  df[“특정열”] ==특정성분 형태로 조건을 넣을 수도 있다)
    names_with_prop.loc[ (names_with_prop["year"] == 2011) & ( names_with_prop["sex"] == "M")].shape

    top1000_names.loc[ (top1000_names["year"] == 2011) & (top1000_names["sex"] == "M" )].shape

    image
    사용자정의함수를 이용해서, 각 그룹화결과물에 대해, births열을 내림차순후, 상위1000개만 뽑은 것을 확인할 수 있다.



(데이터분석3에 이은 것)

데이터분석 4 – 각 연도에 따른, 성별의 전체 출생횟수 대비 TOP 1000이름들의 출생횟수 비중의 합 산출하기 by 피벗테이블

각 연도별 각 성별의, 전체 출생횟수 대비, 상위 top1000개의 출생횟수 비중이, 어떻게 변화되었는지 알아보자.
이전에, 각 (연도, 성별)별 그룹의 전체 출생횟수 대비, 각 이름들의 출생횟수 비중을 의미하는 “prop”열을 만들어놨었다.
이 것을 이용해 pivot_table을 만들면 굉장히 편할 것이다.

먼저 top1000_names.head() 를 통해 데이터를 다시한번 살펴보자.  각 연도별/성별별 그룹마다 상위1000개의 행들만 나타나있다.
image


  • 먼저, top1000_names에서, pivot_table을 이용하여, 
    prop열을 추출열로 하고,
    year열을 index로 가져오는 기준열1
    sex열을 columns로 가져오는 기준열2
    통계함수는 sum을 적용한다.
    연도별 / 성별별 / 비중들의 합이 되므로, 이전에 각 그룹별로 전체 비중을 계산했으니, 각각 1으로 찍혀야하나,
    상위 출생횟수 1000개씩 골라냈으므로, 1이되거나 1이 좀 못될 것이다.
    즉, [ 각 연도/성별별 그룹마다 전체대비 비중계산prop 추가 –> 각 그룹별 TOP 1000개씩 인덱싱 –>
          각 연도/성별별 그룹의 상위1000개, prop열 추출 = 자동으로 전체대비 상위1000개차지하는 비중의 합을 계산 ]
    빨간색은 데이터분석2번 –> 파란색은 데이터분석3번 –> 보라색은 지금 한 것이다.

    top1000_props = top1000_names.pivot_table(values="prop", index="year", columns="sex", aggfunc="sum")
    top1000_props.head(10)
    image

  • 이제 각 연도별/성별별/ 전체출생횟수대비, 상위1000개의 출생횟수비중의합을 라인플롯으로 그려보고 수정해주자.

    ax = top1000_props.plot() 을 통해 그리고
    ax.set_xticks( range(1880, 2020, 10))    ---------> 파이썬의 range정수 리스트 생성
    ax.set_yticks( np.arange(0, 1.3, 0.1)) 을 통해서,  ---> 넘파이의 np.arangearray리스트로 실수/ 행렬까지 가능
    x축은 1880년부터 2020년 전까지, 10의 간격으로 / y축은 0부터 1.3전까지 0.1간격으로 주자.

    image
    간단히 해석해보면)
    1880년도 같은 경우, 비중이 1이다.  1880년에 태어난 아이들의 이름은,, 거의 상위1000개 아름안에 다 들어간다.
    그러나 시간이 흐를 수록, 상위1000개의 이름의 비중이 줄어든다.
    즉, 시간이 지남에 따라 인기는 이름의 비중은 내려가고, 이름에 개성이 생기고 있다.



데이터 분석 5 – 각 연도에 따른, 특정 이름들의 , 출생횟수 변화 추이 분석하기 by 피벗테이블

top1000개 중 몇개의 이름들만 대상으로 하여, 연도에 따른 출생횟수 변화추이를 분석하자.
기존의 top1000_names를 그대로 이용할 것이다. (각 연도별/성별별/ 탑1000개의 이름데이터들이 나와있다)
top1000_names.head()
image
피벗테이블을 활용할 것이므로, 해석을 하자면
각 연도에 따른 –> 기준열1 (index)
특정 이름들의 –> 기준열2(columns)  + 특정이름이라는 것은,,  비 인기이름들은 연도별로,, 출생횟수가 아예 없는 것도 있으니 NaN으로 뜬다
                                                                           그것들 베재하고 인기있는 것들만 살펴보자.
출생횟수 –> 추출열(values)
변화 –> 통계함수(aggfunc)는 sum으로해서,, 이름별,, 출생횟수의 합으로 변화를 살펴야한다

  • top_names_births = top1000_names.pivot_table(values="births", index="year", columns="name", aggfunc="sum")
    top_names_births.head()
    image
    이름이 7031가지나 되고 그것이 columns으로 나타난다


  • 위의 7031가지 이름들 중에, 열인덱싱으로, 특별한 이름 4개만 가져와보자.
    top_names_births_subset = top_names_births[ ["John", "Harry", "Mary", "Marilyn"] ]
    top_names_births_subset.head()
    image


  • top_names_births_subset 을 가지고 라인플롯을 그려보자.
    이 때, plot()의 인자로, subplots =True를 주게 되면, 하나의 ax(axes좌표평면)에 각 칼럼들=각 라인들이 subplot안에 독립되어 그려진다.

    ax = top_names_births_subset.plot( subplots = True , fontsize = 8)
    image
    간단한 해석 )  나머지이름들은 20세기 전체적으로 인기가 있었는데 반면, Marilyn은 20세기 중반에 반짝 인기 있다가 사라졌다.



데이터분석 6 – 남아의 이름 마지막글자에 따른 출생횟수가, 연도에 따라, 어떻게 변화하였는지 분석하기 by 람다/피벗테이블

저명한 연구자에 따르면, 지난 100년동안 남아아이름의 마지막글자의 분포가 급격하게 변화하였다고 알려왔다. 그것을 조사해보자.
처음 사용한 데이터 names에다가,  람다함수를 정의한 뒤, 적용해볼 것이다.
image

  • 먼저 람다함수를 하나 정의해줄 것이다.
    어떤 시리즈(열1개)에 대하여, 각 성분(x)의 마지막 글자( x[-1])를 추출하여 반환하는 함수다.
    get_last_letter = lambda x: x[-1]

  • names의 name열 에다가 .apply()를 통해 람다함수를 적용하게 되면, 각 이름의 마지막 글자만 추출된 열(시리즈)이 생성될 것이다.

    names["name"].apply(get_last_letter).head()
    image

    이것을 names의 새로운 열로 추가해주자.
    names["last_letters"]= names["name"].apply(get_last_letter)
    names.head()
    image


  • (람다함수로 마지막글자 추출후반환 정의 –> name열에 적용-> 새로운 열로 추가)의 과정을 거쳐
    이제 name의 마지막글자를 새로운 열로 추가한 names데이터를 가지고  피벗테이블사용할 것이다.
    < groupby는 그룹화기준 딕셔너리를 통해, 1개의 그룹화결과물을 보고, 사용자정의 def함수를 적용시킬 때 사용했고
      pivot_table은 2개의 기준열을 index, columns로 가져오면서, 통계함수를 빠르게 적용시킬 때 사용했었다.>

    남아이름 마지막글자에 따른 출생횟수가, 연도에 따라, 어떻게 변화(총합)하였는지 분석 이므로
    추출열 = 출생횟수 / index 기준열1 = 마지막글자열 /  columns 기준열2 = 남아(성별) - 기준열3 = 연도(계층적컬럼)/ 통계함수 = sum

    names.pivot_table(values = "births", index ="last_letters", columns = ["sex", "year"] , aggfunc = "sum")
    image


  • 남아에 대한 정보만 필요하므로 크게 잘라내기 위해, 성별을 연도보다 컬럼의 상위계층으로 두었었다.
    (나중에 남자 / 여자 따로 plot으로 나타낼 것이다. )
    위 피벗테이블을 새로운 변수 last_letters_table에 먼저 담아둔다.
    last_letters_table = names.pivot_table(values = "births", index ="last_letters", columns = ["sex", "year"] , aggfunc = "sum")

  • 연도가 너무 많으므로 df.reindex()함수를 사용해서 특정컬럼만 골라낼 수 있다. 이 때 계층적 컬럼이므로 level인자도 같이주어야한다
    (13강에서는 reindex(columns = [  , , ]를 통해, 기존 있던 칼럼들은 다 기입하되, 순서만 다르게 주어서, 칼럼 순서바꾸기로 쓰였었다.)

    three_years_subtable = last_letters_table.reindex(columns=[1910, 1960, 2010] , level ="year")
    three_years_subtable
    image


  • 이 때, three_years_subtable에 .sum()함수를 적용하면 기본적으로 가장 하위층의 각 열마다 총합을 구해준다.
    즉, 성별에 따라 / 각 연도별 / 마지막글자 a부터~z까지의 총 출생횟수의 합이 나온다.
    이것을 구하는 이유는, a~z까지 전체 출생횟수에 대한 각 a, b, c 들의 비중을 구하기 위해서다.
    three_years_subtable.sum()
    image

    이제 전체에 대한 비중을 구하는 식인 three_years_subtable / three_years_subtable.sum()를 계산하여
     비중 dataframe을 따로 변수에 저장한다.

    three_years_letters_prop = three_years_subtable / three_years_subtable.sum()
    three_years_letters_prop.head()
    image
    결과적으로 얻은 결과물은,
    각 성별 / 연도에 따른, 마지막글자를 a~z로 가진 아이의 출생횟수의 상대적 비중을 산출한 것이다.


  • 이번에는 ax = df.plot()으로 바로 안그리고, figure를 생성하면서 plot을 그려보자.
    figure와 axes를 만들고, figure안에 subplot을  2x1의 2개로 만든다.

    fig, axes = plt.subplots( 2, 1)
    image
    이 때, 한 fig에 대해서 subplot이 2개면 –> axes1차원 리스트의 형태로 2개가 반환된다. axes[0]과 axes[1]

  • 첫번 째 axes인  axes[0]을 plot()함수의 ax인자로 주어 그리되,
    열인덱싱으로 M(남아)만 / 바플롯 / 첫번째 axes / title도 동시에 준다.
    (계층적 인덱스라도, 가장 상위계층인 M/ F열은 바로 인덱싱하면된다 ㅋ)

    three_years_letters_prop["M"].plot(kind="bar", ax=axes[0], title="Male")
    image


    F열에 대해서도 똑같이 그리되, 2번째 좌표평면에다가 / title을 바꿔서 그려준다.

    three_years_letters_prop["F"].plot(kind="bar", ax=axes[1], title="Female")
    image

    글자가 겹쳐보일 때는, plt.tight_layout() 를 적용해준다.
    image
    간단히 분석)
    여아(Female)의 경우 – 마지막글자가 e인 아이가 태어난 횟수는 시간이 50년씩 흐름에 따라 감소를 했다.
    남아의 경우에는 – 마지막 글자가 n인 아이가 50년씩 지나면서 거의 2배이상 증가했다.


데이터분석 7 – 특정 몇개의 글자에 대해, 연도에 따른 ,남아 출생횟수의 비중의 시간적변화를 분석해보자.

현재까지 구해진 last_letters_table 데이터를 이용할 것이다.

last_letters_table.head()
image


  • three_years_subtable에 적용했던 것처럼,
    데이터의 각 열방향의 총합( df.sum() )을 전체로 보고 –> 각 성분의 letters의 비중을 구하는 방식으로
    각 성별에 따른 / 모든 각 연도별(각 열별) 마지막글자 a~z까지의 출생횟수 총합에 대한 각 마지막글자 a,b,c,들의 출생횟수 비중을 구한다.

    letters_prop = last_letters_table / last_letters_table.sum()
    letters_prop.head()
    image


  • 이제 .loc []행인덱싱을 통해서 마지막글자가 d, n, y인 애들만 추출하고 / 열인덱싱의 경우에는 남아인 M열(계층중 상위계층)만 인덱싱하자.

    dny_prop = letters_prop.loc[["d", "n", "y"], "M"]
    image

    행이 짧고 열이 너무 기므로(1880~2014) 행과 열을 바꾸는 함수 .tranpose()를 적용시켜보자.
    dny_prop.transpose()
    image

    transpose()한 것을 바로 lineplot()을 그려보자
    dny_prop.transpose().plot()
    image
    간단한 해석) d와 y로 끝나는 남자아이는 비중의 변화가 별로 없던 것에 비해
    n으로 끝나는 남자아이의 출생횟수 변화는   1960년이 넘어가면서부터   비중이 드라마틱하게 늘어났다.


< pivot_table을 이용하면 아주 쉽고 빠르게 통계량을 구할 수 있다는 것에 주목하자>


groupby() 그룹화)

각 ~별 -> 기준열
~~ 의  -> 기준열2
~~의 평균/합 을 계산 -> 추출열
평균/합 -> 마지막에 적용할 통계함수


pivot_table)

values : 추출열 / index : index로 가져올 기준열1 / columns : 칼럼으로 가져올 기준열2 / aggfunc : 통계함수(문자열)  / fill_value = 0 : NaN을 0으로

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라는 코드를 식별자를 하여, 각 유권자별 데이터가 나와있다.

image

head()와 shape와 columns까지 확인해보자.
image



데이터 분석-1 : 각 후보별 전체지역 득표수

각 후보들별로 전체지역 득표수를 계산해보자.
이전 방식이라면 후보를 의미하는 candidate의 유니크한 값들을 추출한 다음, 반복문을 돌리면서 마스크로 votes열만 뽑아 낸 뒤, 통계량을 계산했다.
데이터 그룹화기능을 사용하면, 이를 한 줄에 끝낼 수 있다.
( 각 ~별 -> groupby의 기준열로 들어간다.)
( ~를 계산 -> 데이터그룹화의 결과물에 [ "칼럼명" ] 으로 추출한다)

  1. 각 후보별 득표수를 계산할 것이므로, 각 후보의 unique한 이름들만 확인해준다.(분석과정에서 필요한 것은 아니다)
    primary["candidate"].unique()
    image
  2. primary 전체 DataFrame에 대해서 groupby()를 할 것인데, 기준열이 candidate열 이다.
    각 후보별로 데이터를 계산하므로,groupby()의 기준열로 쓰면, 각 성분인 후보들을 기준으로 데이터가 그룹화되서 계산할 수 있다.
    그룹화된 결과물에서 DataFrame 전체가 아니고, votes열만 필요하므로, 그룹화된 결과물에 열인덱싱 처리를 해준다.
    마지막으로, 득표수를 계산하려면, 그룹별로 votes열의 합을 구해야하기 때문에 sum()함수를 적용한다.
    primary.groupby("candidate")["votes"].sum()
    image
  3. 투표수(성분)별 오름차순으로 보기 좋게 정렬하기 위해서, 맨 뒤에 .sort_values()적용해서 새로운 변수에 담아준다.
    (변수에 담아야지, plot을 그릴 수 있다!)
    이 때, by= 정렬기준열 을 명시하지않은 것은, 열이 하나(votes열) 밖이기 때문이다.
    candidate_to_votes_s = primary.groupby("candidate")["votes"].sum().sort_values()
    image
  4. 후보별 전체지역 득표수을 정렬한 것을, 수평 바 plot으로 나타내자.
    candidate_to_votes_s.plot(kind="barh", fontsize=8)
    image



(my :  각 ~~ 별 –> ~~열을 groupby()함수의 기준열로 삼아라! ,  a와 b의 ~ –> a + b (배반)으로 포함하는 열을 기준열로 삼아라!)

데이터 분석 – 2 : 각 주별, 공화당과 민주당의, 득표비율 계산

다시 데이터의 head()를 보자.
primary.head()
image

각 주별 ----------> state열이 기준열1
공화당과 민주당 –> party열이 기준열2
2가지 열을 groupby()함수의 [기준열]로 사용할 것이다.
득표비율 --------> votes열을 데이터그룹화의 [추출열]로 사용할 것이다.

  1. 데이터그룹화의 기준열을 state, party순으로 주어서, 각 주별 – 공화당/민주당 – 득표수(합)을 계산해보자.
    계층적인덱스가 주 – 당 형태의 Series가 얻어질 것이다.
    아직까지 우리가 원하는 모양(공화당과 민주당의 각 주별 득표비율)이 아니므로, 변수에 저장해주자.
    state_party_votes_s = primary.groupby( ["state","party"] ) ["votes"].sum()
    image
  2. 비율을 구하기 위해서, 각 주의 전체 득표수를 나누어줘야한다.
    기준열에 state열만 넣어서, votes열을 추출하여 sum()함수를 쓰면, 각 주별 전체 득표수가 나오므로
    계산해서 변수에 담아주자.
    state_to_votes_s = primary.groupby("state")["votes"].sum()
    image
  3. 이제 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()
    image
  4. 이제 비율을 바탕으로 bar plot을 그려보자.
    이 때, 민주당/공화당(2번째 층 인덱스)를 unstack()으로 컬럼으로 올리고 barplot을 그리자.
    컬럼으로 보내면, 인덱스인 각 주(state)가 수평y축에 , 칼럼인 [민주/공화당]이 범례에 나타나면서 각 주별 2개의 막대가 생길 것이다.
    이 때, stacked=True까지 주면, 이 나타나면서 막대에 합쳐진, 비율을 가지게 될 것이다.
    image
    간단히 분석해보자면,  대부분의 주가 공화당이 득표가 우세하고, 몇몇 주는, 민주당이 장악하였다.


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다 보이도록 수정하기


이번에는 집계함수를 직접 정의하여, 집계를 수행해보자.

  1. 통계(집계)함수를 정의하는데, func이라는 함수는, 데이터그룹화의 결과물(agg_df)에 대해, sort_values()함수를 이용해 votes열을 내림차순으로 정렬하고, 그 중 첫번째 성분을 뽑는 .iloc[0]으로 인덱싱하여,
    대입되는 그룹화결과물의, votes열에 내림차순 정렬 후 1번째 행만 뽑기 = 가장 득표수가 많은 행을 뽑아낼 것이다.
    func = lambda agg_df: agg_df.sort_values("votes", ascending=False ).iloc[0]
    (당선된 후보 = 투표수가 많은 후보 =  투표열을 내림차순 정열후 1번째 행인 후보)
    image
  2. primary의 head()를 다시 한번 살핀 뒤, county를 대변하는 코드인 fips열을 그룹화 기준열로 사용해서 그룹화하고,
    그룹화된 결과물에 사용자 정의함수를 사용할 수 있게 해주는 .agg()함수에다가,
    그룹화결과물을 인자로 받아 votes열을 추출하여 votes열의 가장 큰 수(득표수가 가장 많아 당선)를 뽑아내는 func 함수를 인자로 주자.
    그럼 primary를 fips(county)별로 그룹화한 결과물에, func함수를 적용하여, 각 county별 <votes순으로 내림차순하여, 득표수가 가장 큰 행이 후보이름 + 당명과 함께>을 뽑아내진다.
    이것을 winners라는 변수에 담아서 살펴보자.
    winners = primary.groupby("fips").agg(func)
    winners
    image
    살펴보면) 각 county별 votes수가 가장 많은 행을 뽑아서 –> 그 county에 가장 많은 득표를 얻은 사람 + 당 까지 알 수 있다.

  3. primary데이터에서 얻은 winners라는 각 county별 최다득표 후보와 당 데이터에다가
    각 county별 백인유권자 정보를 붙혀넣어야한다!
    앞서 read한 counties라는 df에서 얻어와야한다. columns 중에 RHI825214라는 칼럼이, 해당 county별 백인 윤권자 비율이다.
    counties["RHI825214"].head()
    image
    이winnders와 counrties의 county에 대한 행의 갯수가 서로 다르나, 병합의 key열(들or인덱스)을 지정해서,
    각각 동일한 기준성분에 대해 m x n형태로 합쳐주는 것
    pd.merge()함수라고 배웠다.
    merging을 이용해서, fips를 기준으로 winner에다가county의 백인유권자 정보를 병합시켜보자.
    (한쪽 데이터를 고정시켜서 병합시킬 때는, how=left or right인자)

  4. 이제 counties에서는 fips열과 rhi825214열만 병합시킬 것이므로, merge()의 인자에서, 열인덱싱을 리스트형식으로 넣어준다.
    그리고, 데이터를 확인해보면,  winners에서는 fips가 그룹화의 기준이 된 결과 index로 있고 / counties에서는 fips가 이므로,
    image
    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")
    image
    새로운 데이터를 보게되면, 인덱스에 있던 fips가, 새로운데이터와 겹치는 부분으로 mxn형태로, 열로 왔고 우측에는 백인유권자 비율이 붙은 것을 확인할 수 있다.
  5. 이제 백인유권자 비율의 컬럼(RHI825214)의 이름을 바꿔주자. rename()함수의 인자로, 컬럼 = {딕셔너리}형태로 바꿔준다.
    이것을 다시 기존 데이터에 대입하면 바뀐 데이터가 된다.
    winners_county_races = winners_county_races.rename(columns={"RHI825214" : "white_pcts"})
    winners_county_races.head()
    image
  6. 이렇게 얻어진, 각 county별 최다득표자(winner)의 백인유권자 비율까지 표시된 데이터에다가,
    party와, candidate를 그룹화 한뒤, 각 정당별 후보자들의 백인유권자 비율의 평균을 계산해보자.
    ( 각 county별 최다득표자들만 모았으니, 각 county별 당선자들이다. 이제 county를 잊고, 정당 별 후보자=당선자의 백인비율을 보는 것이다)
    winners_county_white_pcts = winners_county_races.groupby(["party", "candidate"])["white_pcts"].mean()
    winners_county_white_pcts
    image
  7. 수평 바 플롯을 그려보자.
    winners_county_white_pcts.plot(kind="barh", fontsize=8)
    image
  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()

    image
    간단히 살펴보면 ) 공화당 후보(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
image


2.
primary.pivot_table(values="fraction_votes", index="state_abbreviation", columns="party", aggfunc="mean")
values = fraction_votes(해당후보의 득표율)을 가지고, 통계함수를 계산할 것인데,
index에는 state의 축약어 /  columns에서는 정당 으로 가져오면서,   2개를 기준으로 그룹화 한뒤,
aggfunc를 통해 <해당 후보의 득표율의> mean(평균값)을 구한다.

각 주별(축약어), 정당들의, 득표율, 평균
image

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)})
image
위 DataFrame에 key1과 key2에는 중복된 값이 있는 것을 확인할 수 있다.

이 때, key1열이 a인 행들에 대해, data1의 평균을 구하고 싶다고 가정하자.
(이전까지는 key1의 유니크한 값으로 마스크 –> 반복문 (df.loc( 마스크, data1열)) 을 통해 평균을 계산했었다. 하지만, 데이터 그룹화를 통해 더 쉽게 할 수 있다)

데이터 그룹화를 하기 위해서는, Series나 DataFramegroupby()함수를 실행할 수 있다.

  • 통계량을 계산할 열(Series) . groupby(  기준이 될 열(Series)  ) 형식으로 실행하게 되는데,
    grouped = df["data1"].groupby( df["key1"])를 통해
    key1열을 데이터그룹화를 한 다음,  data1열의 통계량을 계산할 준비를 한다.
    image
    값을 확인할 수 없는 이유는 , key1열을 기준으로 data1열을 그룹화만 했을 뿐이며, 통계함수는 사용하지 않았기 때문이다.
    grouped에는 ket1의 값인 a를 포함하는 행과 b를 포함하는 행을 각각 그룹화하여  동시에  가지고 있다.
  • 이렇게 그룹화까지만 된 것인 grouped 를 이용해서, 통계함수로 통계량을 계산한다.
    grouped.mean()
    image

이러한 groupby()함수의 절차를 [  splitapplycombine ] 이라고 한다.
기준열을 지정하여 특정열을 그룹별로 나누고 – 각 그룹에 통계함수를 적용하고 – 최종적인 통계량이 산출된 것은 통합해서 표시해주기 때문이다.
(my :  groupby로 데이터를 그룹화 했으면, 반드시 통계함수를 적용시켜야하는구나. 통계량 구할라고 하는 거구나!)


이제 그룹화의 기준이 될 열을 2개 이상 지정할 수도 있다. 2개 열의 성분이 모두 같은 것만 하나의 그룹이 된다.
이 때, 그룹화의 기준열이 2개이면, 계층적 인덱스로 적용된 Series가 나온다.

  • means = df["data1"].groupby( [ df["key1"], df["key2"]]).mean() 를 통해, 기준열2개로 data1열을 그룹화 후 평균까지 구하자.
    image
    나온 결과에서, 기준열이 2개면, 계층적 인덱스가 적용된 Series가 나온다.
    이 때,  unstactk()함수를 적용해서 최하위 인덱스를 칼럼으로 올린 뒤, 데이터를 분석해도 된다.
    image

DataFrame을 데이터그룹화(groupby)하기

지금까지는 데이터 그룹화(groupby)를 적용시키는 것에  특정열 1개를 Series로 주었는데,
DataFrame에 대해서도 groupby()를 적용시킬 수 있다.
Series에서 했던 것과는 방식이 조금 달라진다.
- Series 의 데이터 그룹화 :  특정열인덱싱.groupby ( 기준열인덱싱
- DataFrame의 데이터 그룹화 :  df . groupby ( “기준이 될 컬럼명”) 를 통해 특정열이 아닌, df의 모든 열에 대해서 통계량이 계산된다.

  • df.groupby("key1").mean() 를 적용시켜보자.
    image
  • df.groupby("key1").count() 를 통해, 각 key1열의 값을 가지는 행들의 갯수를 세서 산출해줄 수 있다.
    image
  • 2개의 열을 기준으로 할 수도 있다. 마찬가지로 인자에, 리스트형식으로 칼럼명을 넣어준다.
    image


DataFrame을 데이터 그룹화 해서, 특정열x 전체 열을 그룹화 하더라도,  특정열에 대한 통계량만 산출할 수 도 있다.
그룹화결과물에, 통계함수를 적용하기 에 [ “컬럼명”]으로 뽑아내면된다.

  • df.groupby( ["key1","key2"])["data2"].mean()
    image



반복문을 이용하여, 그룹화 직후의 결과물 확인해보기(통계함수 적용하기전에)

그룹화를 수행한 직후의 결과물을 확인할 수 없었으나, 반복문을 통해 그룹화에 대한 결과물 확인이 가능하다.

key1 이라는 열에 대해, name, group이라 2변수로 매 반복문마다 값을 받아서, 각각의 print해보자.

  • for name, group in df.groupby("key1") :
         print(name)
         print(group)
    key1이라는 열의 각 성분 별로 그룹화를 시켰는데,
    name이라는 변수에는 각 그룹으로 나뉘는 기준열의 성분이 들어가서 출력되며, group에서는 기준성분을 포함하는 행들을, 모든 열별로 출력이 된다.
    image

만약 기준열이 2개라면, name변수 자리에 소괄호()를 이용해서 2개의 변수에 각 기준열의 성분을 받아야한다.

  • for (k1, k2), group in df.groupby( ["key1", "key2"]) :
         print(k1, k2)
         print(group)
    image



딕셔너리를 이용해서 그룹화 직후의 결과물 보기

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라는 키값 : 그룹화된 결과물  } 형태로  나타난다.
    image
  • 이것을 딕셔너리 열인덱싱(키 인덱싱) 하여 b에 대한 그룹화 결과물만 볼 수 있다.
    pieces["b"]
    image



기준열 대신 별로도 정의된 Series나 딕셔너리를 이용하여,  매핑 & 데이터 그룹화

df2 = pd.DataFrame(np.random.randn(5, 5),
                    columns=['a', 'b', 'c', 'd', 'e'],
                    index=['Joe', 'Steve', 'Wes', 'Jim', 'Travis'])

image

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'}
image

이제 groupby를 통화 그룹화를 할 때, 기준이 되는 인자에 딕셔너리를 넣고, axis를 1로 주어, 열 방향(→)으로 지정하여
(위에서 특정열에 대한 성분을 기준으로,  그 값을 포함하는 <index(행들)>를 그룹화 했던 것과는 다르게,)
 컬럼들을 그룹화한다. 이 때, 딕셔너리의 키값을 가진 컬럼들이,  밸류값들로 더 작게 매핑되서 컬럼들을 그룹화 한다.
(axis 인자를 안 주었을 때는, 기준열의 성분을 포함하는 행들을, 그룹화하였고
딕셔너리로 매핑 +  aixs =1을 준 경우에는, 해당 매핑값을 기준으로 해당 값에 속하는 칼럼들을 모아놓는다.

  • dict( list( df2.groupby(map_dict, axis=1) ) ) 를 통해, 
    axis=1으로 컬럼들을 그룹화 할 것이고, 딕셔너리를 통해 매핑하면서 그룹화한다.
    그룹화된 결과물을 보기 위해서, list로 쪼개고, 딕셔너리에 담았다.
    image
  • 그룹화결과물을 바로 보지 말고, 통계함수를 적용해서 합계를 보자.
    df2.groupby(map_dict, axis=1).sum()
    image
    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()를 통해 그룹화된 열의 갯수를 확인할 수 있다.
    image


이렇게 그룹화된 데이터에 적용할 수 있는 통계함수는 아래와 같다.
image



데이터 그룹화 결과물에 통계함수가 아닌, 사용자 정의 함수 적용하기

데이터 그룹화를 통해 얻어진 결과물에, 통계함수 외 사용자 정의함수를 적용할 수 도 있다.

먼저, df의 key1열을 기준으로 데이터그룹화 시켜놓고 변수에 저장한 뒤, 함수를 새로 정의할 것이다.
peak_to_peak()함수를 정의하는데, 받은 array의 < 각 열>마다 최대값 – 최소값을 반환하는 함수다.

grouped = df.groupby("key1")
def peak_to_peak(arr):
     return arr.max() - arr.min()

그룹화된 결과물에  agg()함수를 적용하게 되면,  그룹화된 각 그룹마다 사용자 정의함수(각 열의 최소-최대)를 적용할 수 있게 해준다.

  • grouped.agg( peak_to_peak )
    image

데이터 그룹화의 결과물에 .agg()를 이용해서, 일반적인 통계함수도 적용시킬 수 있다.
이 때, 인자에 “문자열”로 해당 함수를 호출하게 된다.

  • grouped.agg( "std" )  를 통해,  문자열에 std로 입력하면, 각 그룹들에 표준편차를 구할 수 있다.
    image



그룹화된 결과물에 대한 전체 통계량 보기

그룹화된 결과물에 .describe()를 통해 전체 통계량을 확인할 수 있다.

  • grouped.describe()
    image

중복된 행 제거하기

df = pd.DataFrame({'k1': ['one'] * 3 + ['two'] * 4,
                    'k2': [1, 1, 2, 3, 3, 4, 4]})
image
위와 같이 딕셔너리형태의 value값에   [ 성분 ] * n  또는 직접 입력하여, 위<->아래 행이 모두 중복된 성분을 가지는 행이, 여러개 있는 DataFrame이 있다.

  • df.duplicated()함수를 이용해, 성분이 중복되는 행이 있을 때, 아래 행에다가 True를 나타내는 Boolean 마스크를 뽑을 수 있다.
    image
    이 것을, 마스크로 인덱싱하여 중복된 행을 제거할 수 있지지만
  • df.drop_duplicates()를 사용하여, 중복된 행들이 제거되고 unique한 행들만 얻을 수 있다
    image

이 때, 새로운 열(0~6까지를 성분으로 가지는 )을 추가해서, 완전히 중복되는 행이 없도록 만들어보자.
df["v1"] = np.arange(7)
image

완전히 중복된 행이 없는 상태에서, drop_duplicates( )함수에 인자로서, 중복검사의 기준이 되는 열을 지정할 수 있다.
이 때, 중복된 행을 제거하는 과정에서 아무 인자도 안주면,  중복 되는 것 중 가장 첫번째 껏만 남긴다.

  • df.drop_duplicates( ["k1" ] ) 는 k1열만 기준으로 중복된 행을 제거한다.
    image
  • df.drop_duplicates( ["k1", "k2"] , keep="last" )을 통해 k1열과 k2열을 기준으로 모두 중복된 행을 제거하는데,
    keep인자에 last를 통해, 중복 행 중, 가장 마지막행만 남길 수 있다.
    (default 혹은 keep = “first”는 가장 첫 행만 남긴다,
    image




데이터 매핑(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]})

image

food라는 열을 더 작은 가지수로 매핑하기 위해 다음과 같은 딕셔너리를 생성한다.
food열의 unique한 성분(소문자)이 키값 되고  :  더 작은 범위로 매핑시킬 값들이 value가 된다.
meat_to_animal = {
     'bacon': 'pig',
     'pulled pork': 'pig',
     'pastrami': 'cow',
     'corned beef': 'cow',
     'honey ham': 'pig',
     'nova lox': 'salmon'
}
image

사용자 정의 함수인 .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” ] 에 하나씩 담는다.
    image


image



값 치환하기 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.])

image
위 Series에서 몇몇 값들은 많이 벗어난 이상치(outlier)를 확인할 수 있다.

  • 이상치를 NaN으로 치환해보자.
    s2 = s.replace(-999, np.nan)
    image
  • NaN도 다른 값으로 치환할 수 있다.
    s3 = s2.replace(np.nan, 0)
    image
    하지만, NaN을 치환할 때는, fillna( 값 ) 을 통해 더  간편하다.
    s2.fillna(0)
    image



카테고리 자료형(범주형 데이터) 생성, 추가, 대소관계

지금까지의 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']})
image

df3 가운데 하나의 열을 카테고리 자료형으로 변환해보자.
raw_grade열은 a,b,e중 하나의 값을 가질 수 있는데, 단순히 문자열로 저장되어있다. 이를 카테고리 자료형으로 치환해보자.

  • 카테고리 자료형으로 만들고 싶은 열에 .astype()함수를 적용하고, 그 type을 “category”로 주어 새로운 열로 추가해보자.
    df3["grade"] = df3["raw_grade"].astype("category")
    image
    열 인덱싱을 통해서, 카테고리 자료형의 Series라는 것을 확인해보자.
    image
    a, b, e 3가지 카테고리 자료형을 가질 수 있는 것을 확인할 수 있다.
  • 카테고리의 값을 확인 하는 방법은,  열인덱싱.cat.categories 를 입력하면 된다.
    df3["grade"].cat.categories
    image
  • 카테고리의 기존값을 다른이름으로 새롭게 정의해 줄 수 있다. cat.categories로 확인된, 각 순서대로 대응되어서 들어간다.
    df3["grade"].cat.categories = ["very good", "good", "very bad"]
    image
    (my : cat.categories는 카테고리 자료형 특정열을 확인 + 이름바꾸기가 가능하구나.)
  • 카테고리의 값을 추가해 줄 수 도 있다. cat.set_categories () 함수에 리스트 형식으로 인자를 넣어줘야한다.
    이 때, 인자에는 기존 카테고리의 값도 포함시켜서 리스트형식으로 넣어주면, 기존 값은 유지하면서
    해당 열을 확인해보면, 카테고리의 값이 늘어나는 것을 확인할 수 있다.
    df3["grade"] = df3["grade"].cat.set_categories( ["very bad", "bad", "medium", "good", "very good" ])
    image
    ( 카테고리열인덱싱.cat.categories 로 확인하는 것이 정상?)


카테고리의 또다른 특징은, cat.set_categories()의 인자에 넣어준 리스트의 순서대로, 대소관계를 가진다는 것이다.
이것은 sort_values(by=”열”)로 확인해보면, 오름차순 정렬 될 때,  1번째 very bad > 4번째 good> 5번째 very good 순서로 정렬이 된다. 이말은 마지막에 넣어준 카테고리값이 가장 크다는 것을 의미한다.

이러한 카테고르의 대소관계의 특징은,  사원 > 대리> 과장> 부장> 사장 같은 자료형에 대해서 효과적으로 정렬할 수 있다.

  • df3.sort_values(by="grade")
    image


숫자 데이터의 카테고리화

연령을 나타내는 열이 숫자로 구성되어 있으며,  일정 나이 기준으로 청년, 성인, 노년 처럼 구간을 나눈 뒤, 관리하면 편해진다.
조건문을 활용해서 나눌 수 도 있지만, 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)
    image
  • cats.codes 를 통해, ages의 각 성분이 몇번째 구간에 속해있는지 정수index처럼 표시되는 것을 알 수 있다.
    cats.codes
    image
    예를 들어,  20이라는 것은 0=첫번째 구간에, 27은 1=두번째 구간에 속한다는 것을 알 수 있다.
  • cats.value_counts() 를 통해서, 값x 각 구간에 따른 성분의 갯수를 확인할 수 있다.
    cats.value_counts()
    image
    (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)
    image
  • 3. 각 구간별 성분의 갯수를 알고 싶으면 .value_counts()를 확인해야한다.
    cats2 = pd.cut(ages, bins, labels= group_names)
    image


각 구간 구분값으로 bins를 정의해서 나누었었는데, 이것이 귀찮다면, pandas에서 알아서 판단해서 데이터의 길이를 잘라주고 구간을 설정해주는 방법도 있다.

data = np.random.rand(20)

image

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 )
    image

한편, pandas에서는 qcut이라는 함수도 제공한다. 지정한 갯수만큼 구간을 정의하는데,
위에 cut()함수는 최대값 쵯소값만 고려해서 구간을 나눈 것에 비해
그 분포가 고려된, 분위 수를 구분값으로 구간을 나누는 함수다.

1000개의 랜덤 1차원 array를 데이터로 해보자. 이 때, 성분인덱싱으로 5개만 보자.
data2 = np.random.randn(1000)
data2[:5]

image

  • cats = pd.qcut(data2, 4)를 통해 4개의 구간을 나누면, 최소값<—>최대값 사이를 4등분 하는 것이 아니라,
    분포까지 고려해서 4분위로 나눈 다음, 구간을 결정하게 된다.(cut함수와 달리, 각 구간의 길이가 동일하다고 말할 수 없다)
    cats = pd.qcut(data2, 4)
    cats
    image
  1. 폴밍끼 2021.04.06 11:16 신고

    감사합니당!!!

계층적 인덱스 - 정렬함수

물류회사에서 고객정보를 관리하는데, 고객의 주소를 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]])
image

첫번 째 층에는 a부터 d까지, 2번째 층에서는 1~3까지의 정수가 나와있다. 실제로 Series.의 index를 확인해보면 MultiIndex라고 표시되어있다.
image

이렇게 계층적 인덱스가 있을 때, 인덱싱을 어떻게 수행하는지 알아보자.
가장 바깥층 인덱스부터 인덱싱하는 것이 원칙이다.

  • s["b"] 를 통해서, 정수로 성분 n-1번째 인덱싱 으로 쓰이는 s[ ], 가장 바깥층을 index명을 적어서, 그 하위계층의 인덱스+성분을 얻게 된다.
    (원래는 정수를 넣어서 s[0]을 하면 1번째 성분, s[1]을 하면 2번째 성분이 나오는 Series의 성분인덱싱으로 쓰이는 곳이었다)
    image
  • 가장 바깥층의 index명으로 범위인덱싱도 할 수 있다.
    s["b":"c"]
    image
  • 인덱스란에 ()소괄호를 넣어, (상위계층,하위계층) 형식으로 넣으면 하위 계층까지 지정해서 인덱싱할 수 있다.
    s[ ("b", 3)]
    image
    확인 결과, 소괄호 없어도 된다. [  ,  ]인덱싱처럼 콤마를 주어 하위계층을 입력하면 [첫번째 층, 두번째 층]을 지정해서 인덱싱할 수 있다.
    s[ "b", 3]
    (원래, []안에 인자가 여러개 들어갔을 때, 같은 위상에 있는 연속적인 값을 입력할 때는 소괄호를 통해 입력한다)
    image
    아! 그래서 행 인덱싱은 따로 .loc[]를 이용했구나, 2차원의 경우 열인덱싱에서 계층적인덱싱하라고??
    열인덱싱란에 [행,렬]을 적어봤자 행렬이 아니라, 계층적인덱싱이다.
    .loc[]의 인덱싱란에 적어야 [행,렬]이 된다.
  • 마찬가지로, s[첫번째 층 인덱스, 두번째층 인덱스]로 인덱싱하는데,  콜론을 이용하여, 2번째 층의 인덱스가 2인 것만 뽑아낼 수 있다.
    s[ : , 2 ]
    image


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"]])

image

  • 복수 계층의 인덱스 와 칼럼에 이름을 붙힐 때는, 파이썬의 리스트 형식으로 주면 된다.
    df.index.names = [ "key1", "key2"]
    df.columns.names = ["city","color"]
    image


DataFrame의 열에 대한 계층적 인덱싱을 해보자.

  • DataFrame의 열도 가장 상위계층을 먼저 인덱싱해야한다.
    df["Seoul"]
    image
  • 이제 상위계층의 하위계층까지 지정해서 인덱싱해보자. 열의 최하위계층까지 간다면 하나의 열만 추출하므로 Series형태로 나올 것이다.
    인덱스는 그대로 계층적 인덱싱을 하고 있다.
    df["Seoul", "Green"]
    image

행의 인덱싱도 기존 인덱싱과 유사하게 최상위계층부터 인덱싱해야한다.

  • df.loc["a"] 를 통해 상위계층 a를 인덱스로 가지는 dataFrame과
    df.loc["a", 1]를 통해 상위a , 하위 1이라는 인덱스를 가지는 하나의 -> Series형태로 얻을 수 있다.
    이때, Series의 인덱스는 기존 DataFrame의 계층적인 컬럼들이 인덱스로 들어가는 것을 확인할 수 있다.
    image


.loc를 이용해서, 행과 열을 동시에 계층적 인덱싱을 할 수 있다( 다만 각 행, 렬에서 순차적으로)

  • 행과 열을 동시에 계층적 인덱싱할 때, 소괄호()를 통해서 상위->하위계층으로 들어갈 수 있다.
    df.loc["b", ("Seoul","Red")]
    상위 b행 ,  상위 Seoul열-> 하위 Red열 인덱싱
    image
    df.loc[("b", 2), "Busan"] 를 통해서,   index( b->2)  / columns Busan만 인덱싱 할 수 있다.
    image
  • 행과 열을 동시에 인덱싱할 때는,  모든 계층을 다 타고 내려가면, 특정 성분이 나올 것이다.
    (행 or 열 하나만 인덱싱하여 최하위까지 가면, Series가 나온다)
    df.loc[("b", 1), ("Seoul", "Green")]
    image


계층적 인덱스에도 인덱스를 기준으로 정렬을 할 수 있다.

이전시간에는 df.sort_index() 만 호출하면 인덱스가 자동으로 오름차순으로 정렬이 됬었다.
여기에 level이라는 인자를 주어, 해당하는 인덱스의 층수(정수 인덱스 : 0,1,2,…)를 주면, 그 층의 인덱스를 오름차순으로 정렬할 수 있다.
(sort_index()에 axis인자를 주지 않으면, 열에 따른 행 방향(axis=0) 기준으로 정렬한다. )

  • df.sort_index(axis=0, level=1) 를 통해,
    행 방향(↓)으로 행index가 + 2번째 층의 인덱스(key2)가 오름차순 되도록 정렬시킨다.
    image
  • level인자에 정수 인덱스가 번거롭다면, 층의 이름을 명시해서 사용할 수 있다.
    image
  • 행에 따른 열 방향(→)으로 columns을 오름차순 정렬하면서, 계층을 명시할 수 있다.
    df.sort_index(axis=1, level=0) 를 통해 1번째 계층인 city를 알파벳 순서로 busan->seoul 순으로 오름차순 정렬 된다.
    df.sort_index(axis=1, level=1) 를 통해 2번째 계층인 color 오름차순으로 green->red 순으로 정렬된다.
    image


계층적 인덱싱에서, 성분(값)기준 정렬을 할 수 있다.
이전 13강에서 sort_values( by ="칼럼명")를 통해  특정 칼럼의 성분을 기준으로 오름차순으로 정렬했었다.
(sort_values by = 칼럼명 은 하나로 묶여서 생각하자)
여기에 ()소괄호를 이용해서 계층적 인덱싱을 하면 된다.

  • df.sort_values(by=("Busan","Green")) 를 통하여,  Busan계층의 Green열의 성분들을 오름차순 정렬이 된다.
    image



계층적 인덱스 - 통계함수

계층적 인덱스에 통계함수를 적용해보자.
이전에는 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를 구분해서 각 열의 성분들의 합을 구한다.
    image
  • df.sum(axis=0, level=1) 를 통해, key2라는 열에는 a-1,2/ b-1,2의 계층이 있으나, 그것을 무시하고
    key2 index의 1과 2가 끊어주는 기준이 된다( 1번째 계층 a,b는 무시되어 서로 섞인다)
    image


level에 원하는 인덱스 또는 컬럼의 특정계층 이름(name)을 명시해서 통계함수를 적용할 수 있다.(컬럼명 아님!)
만약 axis=0이라면, 열에 따라 행방향으로 합을 구하므로, 끊어주는 기준은 행(index)이 된다.
만약 axis=1이라면, 행에 따라 열방향으로 합을 구하므로, 끊어주는 기준은 열(columns)이 된다.
즉, 방향에 따라 끊어서 계산하는 기준이 level이다.
image

  • df.mean(axis=1, level="color") 를 통해서, 행에 따른 열방향의 평균인데,  2번째 계층인 color계층을 기준으로 끊어서 평균을 구한다.
    image



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로 주어야한다!

image

c와 d열의 성분을 계층적 인덱스로 바꿔보자.

  • set_index()함수에 리스트[]형식으로, 계층순서대로 컬럼명을 주면된다.
    (my : 인덱싱할 때는 [ ] 안에 소괄호로 계층을 주고 / 각종 함수에서는 ( )안에  리스트로 계층을 주네?!)
    df3 = df2.set_index( ["c", "d"] )
    그럼 c의 성분들에 대해 d의 성분들이 알아서 박힌다. 그리고 c, d열은 사라진다
    image
  • c열과 d열을 계층적 인덱스로 주면서, 기존의 c열과 d열을 유지하면서 c,d열을 인덱스로 내려주고 싶다면
    set_index()함수의 인자에,  열->index 으로 내려가는 drop을 False로 주면 된다.
    df2.set_index(["c","d"], drop=False)
    image

현재 존재하는 계층적 인덱스를-> 열로 올리면서 동시에 기본 정수를 index로 주는 함수는 reset_index()함수다.

  • df3.reset_index()
    image


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"

image

계층적 인덱스는 아니다. 이 때,  stack()함수를 사용하면, DataFrame의 최하층 컬럼자체가 –> 인덱스의 최하위 index 층으로 붙게 되면서, Series가 된다. 여기서는 컬럼이 단일층이므로, 해당 칼럼이 넘어가버려 Series가 되는 것이다.

  • df4.stack() 를 통해 단일컬럼(number)가  단일 인덱스(city)의 하위계층으로 붙게 되는 것을 확인할 수 있다.
    기존의 칼럼이 사라지면서, Series의 형태가 되어버린다.
    image
    *** 주의할 점은, stack()함수와 set_index()의 작동방식이 다른 것이다.
    (1) set_index( [ “컬럼1”, “컬럼2” ])는  컬럼이 내려와서 기존의 인덱스를 <대체> 해버린다.
         drop=False로, 칼럼1,2를 살릴 수 도 있다.
    (2) stack()함수는 컬럼의 최하위계층이 기존 인덱스의 <최하위계층으로 내러와서 붙는다>

  • df5.unstack()을 통해서, 인덱스의 최하위 계층(number)를 –> 칼럼의 최하위계층으로 올린다
    image

만일 계층적 인덱스 중 특정계층의 index를  columns으로 올리고 싶다면 level인자로 지정해준다.

  • df5.unstack(level=0)를 통해, 첫번째 계층의 index를 --> 컬럼으로 올린다.
    이전에는 df5.unstack()만하여 최하위 계층의 index가  --> 컬럼으로 올라갔었다.
    image
  • level인자를 특정 인덱스명으로 지정해줘도 똑같이 컬럼으로 특정index층이 올라가는 것을 확인할 수 있다.
    image


이제 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"])

image

  • unstack()을 통해 최하위층 index를 칼럼으로 올리자. 이 때, 칼럼으로 올라가면서 index c와 d는 겹치므로, 알아서 2개 행에 담아진다.
    s3.unstack()
    image


이제 좀 더 복잡한 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"
image

  • df6.unstack()을 통해 최하위 index인 number 라는 인덱스명이 –> 칼럼의 최하위층로 올라간다.
    image
  • unstack()을 할 때, level인자를 주어서, 첫번째 계층(city)을 최하위 칼럼으로 올릴 수 도 있다.
    df6.unstack(level=0)
    image
  • stack과 unstack을 동시에 할 수 도 있다.
    df6.unstack(level="city").stack(level="side") 을 통해,
    city 인덱스는 unstack하여 칼럼으로 올리고, 칼럼명 side는 stack()을 통해 index 최하위계층으로 내려보자.
    image

데이터 분석에 있어서, stack/unstack 등의 계층적 인덱싱을 하면 복잡해지므로, 사실상 비추하는 분야이다.

  1. 유아파이썬 2019.06.20 17:09

    계층적 인덱싱을 통해 처리한 자료를 matplotlib을 통해 그래프를 그리고자 합니다. 그런데 X 로 최하위 index를 지정해주고 싶은데 인식하지 못하는것 같습니다. 혹시 가능할까요

    • nittaku 2019.06.20 18:38 신고

      직접봐야할 것 같은데요.. 여러가지 다 해보시고 안되면, 계층적 인덱스를 버려야할 것 같아요. hue같은 인자로 3번째 변수로 두고 색이나 크기로 표현해보시지요~

여러개의 파일을 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)})

image

  • 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")
    image
  • 이제 추가적으로 how=”outer”라는 인자를 주어서, key열의 값 중 한쪽 DataFrame에만 있는 행들도 추가해준다. 없는 쪽의 값은 NaN으로 표시된다. 즉, key의값이 동일한 행들은 n X m조합 + 값이 동일한 행이 없는 경우는 NaN을 포함해서 추가
    pd.merge(df1, df2, on="key" , how = "outer")
    image
  • 다음으로 인자를 how=”left 로 주어보자. 그러면 왼쪽에 있는 df1이 기준이 되어 고정되고, df2는 동일한 키값을 가질 때마다, 여러번 달라 붙혀지게 된다. 만약 달라붙는 df2에 해당 key값이 없는 경우는 NaN으로 붙혀진다.
    pd.merge(df1, df2, on="key", how="left")
    image
    마찬가지로 how=”rightt”를 주게 되면, df2는 고정된 상태에서, df1이 동일한 key값을 가지고 있을 때마다 달라 붙으며, 없는 경우 NaN으로 표시된다.
    image


이번에는 df1, df2 둘다 key열에 중복된 값을 가지는 데이터를 이용해보자.
df3 = pd.DataFrame({"key": list("bbacaab"),
                     "data1": range(7)})
df4 = pd.DataFrame({"key": list("ababd"),
                     "data2": range(5)})
image

  • 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개의행을 가진다.
    image
    마찬가지로 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)})

image

  • pd.merge(df5, df6, left_on="lkey", right_on="rkey")를 수행하게 되면, 왼쪽df5의 lkey와 오른쪽 df6의 rkey를 기준으로 값이 같은 것들끼리 m x n 조합으로 나타나게 된다. 예를 들어, b의 경우 df5는 3개, df6은 1개로 총 3개의 행이 나타나는 것을 확인할 수 있다.
    image


이제, 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'])
image

  • pd.merge(left1, right1, left_on="key", right_index=True)
    왼쪽인자에 들어가는 것은 _on=”열명”을 통해 열을 기준으로하고, 오른쪽인자는 _index=True를 통해 index명을 기준으로 놓으면 된다.
    left1의 key열에는 a가 3개, right1의 index에는 1개가 있으니, 3 x 1 개의 행이 나오는 것을 확인할 수 있다.
    image

이제 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'])
image

  • pd.merge(left2, right2, how="outer", left_index=True, right_index=True)
    두 df 모두, index를 기준으로 merging하도록 옵션을 준다.  그리고 how=”outer”를 넣어서 공통된 값이 없는 행들도 표시되도록 해보자.
    image



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"])
image

  • pandas에서 제공하는concat()함수를 이용해서 리스트형식으로 넣어보자.
    pd.concat([s1, s2, s3]) 를 호출하면, 단순히 index가 증가하는 형태로 연결되어 새로운 Series가 생성된다.
    image
  • 만약, axis=1을 인자로 주면, Series가 아닌, DataFrame의 형태로 각각 열 방향으로 연결된다.
    이 때, 3개의 각 Series는 ab/ced/fg로 공유하는 index가 없었기 때문에, 칼럼들은 default하게 0부터 시작해서 총 3개의 열이 생성된다.
    pd.concat([s1, s2, s3] , axis= 1)
    image

이번에는 s1과 index는 같으면서 성분*5  +  s3를 행 방향으로 단순연결한 새로운 Series s4를 생성해서 concat()해보자
s4 = pd.concat([s1 * 5, s3])
image

  • s1과,  s1의 인덱스와 일부가 같은 s4를  열 방향(axis=1)으로 concat()해보자.
    pd.concat([s1, s4], axis=1) 의 경우, 동일한 index에 대해서는 첫번째 인자s1에다가 s4를 붙히는 것이다.
    index가 같은 a와 b부분에서는 자연스럽게 열방향으로 연결된다.
    image
  • 여러개의 Series를 concat()로 연결할 때, DataFrame에다가 컬럼명을 명시해줄 수 있다.
    어차피 3개의 Series를 axis=1 로 concatenating한다면 3개의 열이 생길 것이다.
    각 열의 컬럼명을 keys=라는 인자로 지정해 줄 수 있다.
    pd.concat( [s1, s2, s3] , axis= 1, keys=["one", "two", "threes"] )
    image



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열로 만들 수 있다.
image

  • DataFrame 2개를 열 방향으로 단순연결해보자.
    pd.concat( [df1,df2], axis=1 )
    image


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'])

image

  • 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)
    image
  1. 양승훈 2020.08.12 00:40

    포스팅 잘봤습니다!!

    궁금한 점이 하나 있는데요,, concatenate 함수를 사용해서 같은 dimension의 데이터들을 합치려고 할 때

    만약에 가지고 있는 데이터들의 수가 너무 많을 때(ex:a1, a2, a3 ...... a299, a300) 하나하나 전부 타이핑하기는 불가능할 것 같은데요

    np.concatenate([a1,a2,a3, ...... a299, a300], axis=0)

    위 상황을 더 간단한 방식으로 합칠 수 있는 방법이 있을까요?

1. Seaborn

matplotlib을 기반으로 만들어진 시각화 라이브러리로 사용방법이 아주 유사하다.
공식사이트 갤러리로 가서 살펴보면 된다.
(https://seaborn.pydata.org/examples/index.html)

image



2. Bokeh

웹 브라우저 상에서의 시각화에 효과적인, Python 인터랙티브 시각화 라이브러리로,
플롯을 html 파일로 export하여, 웹브라우저 상에서 확인할 수 있고, interactive하게 조작이 가능하다.
예를 들어, 선택한 부분만 히스토그램이 활성화되는 기능도 있다.
image

마찬가지로 matplotlib과 사용방식이 유사하다. 공식사이트의 갤러리에서 살펴보자.
(https://bokeh.pydata.org/en/latest/)

image



3. Folium

지리적 데이터 시각화에 특화된 라이브러리로, 자바스크립트 라이브러리인 leaflet.js기반이다.

지도 데이터 사용을 위해 선행되어야 하는 적으며, 설치와 사용법이 간단하다.

공식 깃허브 : https://github.com/python-visualization/folium

image

1. 전체기준열의 각 values값이 포함된 행수의 세기(분포) => 라인플롯 그리기
파읽 읽기 –> shape / head() / columns(칼럼명들만 리스트로 반환) 보기 –>  전체 기준열을 인덱싱한 뒤, .value_counts()로 포함된 행수를 Series로 받기 –>  그 Series의 분포에, .sort_index()를 통해 오름차순 정리하기 –>  새로운 변수에 담아서, 변수.plot()그리기 –> plot그릴 때 반환 받은 axes변수로 xticks(yticks) 정수로 바꿔주기 –> xlim(ylim)으로 범위 늘려주기

image


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플롯 그리기

image



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으로 계산해서 더하기 –> 히스토그램 그리기
image

필요한 패키지 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
image

image

* 데이터.columns를 하면 컬럼명들이 리스트형식으로 반환된다.


데이터 분석하기(1)

1. 전체기준열의 각 values값이 포함된 행수의 세기(분포) => 라인플롯 그리기
파읽 읽기 –> shape / head() / columns(칼럼명들만 리스트로 반환) 보기 –>  전체 기준열을 인덱싱한 뒤, .value_counts()로 포함된 행수를 Series로 받기 –>  그 Series의 분포에, .sort_index()를 통해 오름차순 정리하기 –>  새로운 변수에 담아서, 변수.plot()그리기 –> plot그릴 때 반환 받은 axes변수로 xticks(yticks) 정수로 바꿔주기 –> xlim(ylim)으로 범위 늘려주기

image

첫 번째로, deaths라는 데이터프레임에서 죽은 책의 이름을 의미하는 [ 죽은책 번호(Book of Death)에 따른 –> 죽은사람의 명수(행의 수=사람의 수)를  ] 시각화해보자.  즉, 전체기준열의 각 values값이 포함된 행수의 분포를 보면 된다.
- 각 행이 한 사람을 의미하므로, value_counts()로 세어진 행수 = 사람의 수를 의미하게 된다.

  1. 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명이 죽었고… 를 한눈에 알 수 있다.
    image


라인플롯 그리고 보기 편하게 변형하기

  1. fig없이 바로 데이터.plot()함수를 이용해, 해당 데이터로 라인플롯을 그린다.
    color, marker, linestyle을 인자로 함께 주면서,  데이터.plot()으로 그린 좌표평면을 반환 받는다.
    ax1 = book_nums_to_death_count.plot( color ="k", marker = "o", linestyle = "—")
    image

  2. 1.0부터 5.0로 소수점까지 포함된 xtick을, 정수로 바꿔주기 위해서  좌표평면.set_xticks()함수에 np.arange(1,6)을 이용한다
    ax1.set_xticks(np.arange(1,6))
    image

  3. x축의 범위를 조금 늘려 0부터 6까지 나타내기 위해서
    ax1.set_xlim( [0,6] )
    image
  4. y축은 정수니까, 범위만 조금 늘려준다.
    image

데이터를 시각화해서 보니, 3권에서 가장 많은 사람이 죽었다는 것을 알 수 있다.


image





데이터 분석(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플롯 그리기

image


battles에 대한 데이터를 다시 한번 확인하기 위해 .head()를 확인한다.
image

battles 데이터에서는 전투군과 수비군에 대한 정보가 서술되어있다.

공격군과 수비군의 수(attacker_size, defender_size)의 합이 10,000이상되는 대규모 전투를 뽑은 뒤, 
대규모 전투 별로 병력차이를 비교해보고  Boxplot(수평barh+stacked)으로 시각화 해보자.

  1. 단순히 0부터 정수로 되어있는 index를 전투의 이름(name)으로 바꾼 뒤, battels에 대입하여 덮어씌우자.
    battles = battles.set_index(["name"])
    battles.head()
    image
  2. 공격군과 수비군의 합이 10000만 이상 되는 것을 뽑기 위해, 해당조건으로 boolean마스크를 생성한 뒤 변수에 담아준다.
    large_battles_mask = battles["attacker_size"] + battles["defender_size"] > 10000
    image
  3. 마스크로 행인덱싱 하고, 확보할 열 2개(공격군, 수비군 수)을 열인덱싱하여, 조건에 맞는 행만 뽑아낸 뒤,  새로운 데이터프레임에 담는다.
    large_battles = battles.loc[large_battles_mask, ["attacker_size", "defender_size"]]
    large_battles.head()
    image

  4. 이제 대규모전투만 뽑은 DataFrame에 대해, 각 군의 상대적인 비율을 확인하기위해 bar플롯(수평barh + stacked)을 그려보자.
    large_battles.plot(kind="barh", stacked=True , fontsize = 8)
    image
    공격군+수비군의 합이 만명이 넘어가는 대규모 전투는 총 10개가 있었고,
    bar플롯(stacked)을 통해 병력차이를 확인할 수 있다.

  5. 공격군과 수비군의 효과적인 비율비교를 위해서,  비율을 먼저 계산하여 새로운 열로 추가하고,  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"] )
  6. 각 비율 열 2개만 인덱싱하여 barplot을 그려보자.
    ax3 = large_battles[ ["attacker_pcts" , "defender_pcts"] ].plot( kind = "barh", stacked = True, fontsize = 8)image







데이터 분석(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으로 계산해서 더하기 –> 히스토그램 그리기
image


각의 가문들(attacker_1~4, defender_1~4에 속한 values값들)이 전투에 몇회 참여했는지 를 분석하고, 히스토그램으로 나타내보자.
image

  1. 컬럼 중에 전투에 참여한 가문들이 나와있는 <컬럼명> 발췌해보자.  ( .columns –>칼럼명 리스트 / .columns[ a:b] –> 일부 칼럼명 리스트)
    *** 칼럼명들만 모아서 가지고 열인덱싱[칼럼명들 변수]로 손쉽게 인덱싱할 수 있다. 원래는 필요한 열들만 발췌해야하나, NaN이 껴있기때문에, 필요한 열들의 칼럼명을 뽑아서 다른 처리를 먼저 해줘야하기 때문이다.
    이 때, 컬럼들이 붙어 있으므로 <컬럼명>열 번호로 인덱싱한 뒤, 컬럼 명 변수에 담아서 확인해보자.(5번째 열부터 12번째 열까지 총 8개)
    (cf. battles[4:12]면 열 인덱싱이고 , battles.columns[4:12]는 컬럼명만 가져온다.
    col_names = battles.columns[4:12]
    col_names
    image
  2. 참여가문들이 나와있는 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]
    image
    여러개의 열들을 인덱싱한 뒤, .vaules로 값들을 넣으면 각 열에 대한 values들이 2차원 array로 얻어지는 것을 확인할 수 있다.
    각 열에 대한 values들만 각 열별로 나누어 담기 때문에, index같은 것은 없다.
  3. numpy에서 제공하는 unique()함수를 적용해서, 다시 담아넣자.
    np.unique()함수를 쓰면, 각 열별로 value값들이 2차원 array형태를 가졌는데, 1xn의 1차원 array 중복없이 싹다 모아버린다.
    house_names = np.unique(house_names)
    house_names
    image

  4. 간편하게 none을 제거하기 위해서, 열인덱싱(1차원에서는 n번째 성분을 의미!)으로 boolean마스크를 이용해서,
    성분들 중에 None이 아닌 것만 담도록 인덱싱해서 다시 담아준다. 즉, 성분(각 열로 취급)들 중에 None이 아닌 모든 것들을 인덱싱하는 것이다.
    house_names = house_names[house_names != "None"]
    house_names
    image
    NaN이 제거된 순수한 가문들(8개열의 values)들을 얻을 수 있다.
    해석하자면, 최소 1회 전투에 참여한 모든 가문들의 이름을 모은 것이다.


  5. 이제 위의 가문들을 index로 하고, 성분은 0으로 하는 Series를 하나 정의하자.
    최종 목표인 <가문을 index로 하여, 각 가문에 따른 전투 참여 횟수>의 Series를 만들기 위한 준비이다.
    ( data에 0만 넣으면 모든 index에 대해서 0으로 찍히는 Series가 만들어진다)
    house_to_battle_counts = pd.Series(0, index = house_names)
    house_to_battle_counts
    image
    여기에, 각 열을 기준으로 index에 합산되도록 해서, 전투 참여횟수를 구할 수 있다.
  6. 만들어진  가문이름을 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()
         image
    for col in col_names :
         house_to_battle_counts = house_to_battle_counts.add( battles[col].value_counts() , fill_value = 0)
    image
    총 8개의 열에서, 각 가문이 몇번 등장했는지, 데이터가 나오게 된다.

    해석해보자면,  Lannister가문과 Stark가문이 다른가문에 비해 압도적으로 많이 전투에 참여했다는 것을 알 수 있다.
  7. 이제 이 데이터로 히스토그램을 그려보자.
    ax4 = house_to_battle_counts.hist(bins=10)
    image
    해석)
    대체로 1회~ 3회 정도까지 전투에 개입한 가문이 제일 많고 , 15~18회 전투를 참여한 가문은 1번으로 드물다.
  1. 2018.05.02 18:48

    비밀댓글입니다

    • nittaku 2018.05.02 18:49 신고

      학생이고 초보입니다 더 전문가 찾으셔야합니다^^ 죄송합니다

  2. kay 2018.08.20 14:08

    우와~ 정리가 너무 잘되어 있어요! 다이어그램이나 위 내용 중 데이터분석(2), 데이터분석(3) 같은 내용을 만드실 때에는 어떤 프로그램을 사용하시나요?
    시간 되실 때 공부하기 노하우 또는 공부한 내용 정리하기 같은 contents로 블로그 해주셔도 너무 도움 될 것 같아요!!!

Matplotlib에서는 figure라는 그림단위를 사용하여, 이 안에서 한개 혹은 여러개의 plot을 그리고 관리하도록 지원을 한다.

이 때, figure안에 들어가는 plot하나를 subplot이라 부른다.

figure에 알아보기 앞서 필요한 패키지들과 매직명령어를 import하자
%matplotlib nbagg
import matplotlib.pyplot as plt
import matplotlib

figure –> subplot 으로 세분화해서 그릴 준비하기

  • pyplot에서 제공하는 .figure()함수를 호출해서, 내부가 텅빈 초기 figure를 만들 수 있다.
    image
  • 이제 생성한 피규어 fig 에 subplot을 추가해야한다. add_subplot( , , )을 호출하여 좌표평면을 나타내는 변수(axes)에 받아줘야한다.
    앞에 2개인자는 fig 내에 몇행x몇렬로 subplot을 가질 것인지 지정한다.
    마지막 인자는, 행렬의 몇번째 성분에 그릴 것인지를 지정하는 것이다. 2x2라면 1,2/3,4 시작위치를 지정할 수 있다.
    ax1 = fig.add_subplot(2,2,1) 라는 명령어를 입력하면, 만들었던 피규어의 2x2 중 1번째 위치에서 빈 좌표평면(ax1)이 그려지는 것을 확인할 수 있다.
    image
    마찬가지로 2, 3번째 위치에서 시작하는 subplot의 좌표평면도 나타내자
    image
    이렇게 인터렉티브하게 조작할 수 있는 것은 매직명령어로 옵션을 줘놨기 때문에 가능하다.


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() )
    image
  • axes를 지정해줘서 plot을 그려주자. 첫번 째로, ax1에다가 히스토그램(x값만 있으면 그려지는) 한번 그려보자.
    ax1.hist( np.random.randn(100), bins = 20) - 랜덤정수100개가 속하는 구간을 20개의 구간으로로 나타낸다.
    아래와 같이 첫번째 좌표평면에 그려지게 된다.(fig-subplot-ax가 없었을 땐,  Series.hist(bins=,normed=) )
    image

  • 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(열인덱싱, 열인덱싱) )
    image
    특정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도 반환받자.
image

반환받은 axes에는 2x3짜리 array가 들어가 있으며, 각 성분은 그 위치의 axes에 해당한다. ax1, ax2방식이 아니라, 인덱스로 지정해준 다음 .plot()을 통해 해당 위치에 plot을 그릴 수 있다.
image



plot 모양 변형하기

라인플롯의 경우, 색/마킹기호/라인스타일 등을 지정할 수 있다. plt.plot()으로 라인플롯을 그리면서,
인자로 color, marker, linestyle이라는 값을 줘보자.

  • plt.plot(np.random.randn(30), color="g", marker="o", linestyle="—")
    image
  • 인자를 “” 문자열 안에 한번에 줄 수 도 있다.
    plt.plot(np.random.randn(30), "k.-") : 검은색, 점모양, 일반라인
    image

  • 각종 인자color, marker, linestyle값 목록
    image


라인플롯 이외에,  바 플롯, 히스토그램, 산점도 에는 coloralpha값을 지정할 수 있다. 그리고 좌표평면axes도 ax 인자로 지정해주자.
subplots를 생성해서 그리면서 채워보자. alpha는 투명도를 의미한다.
fig, axes = plt.subplots(2,1)
image

  • 먼저, 데이터를 만들자, 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)
    image
  • 마찬가지로, 수평 바플롯을 그리고 색과 투명도를 주자.
    data.plot(kind="barh", ax=axes[1], color="g", alpha=0.3)
    image
    마찬가지로, 히스토그램이나 산점도도 마찬가지로 입력하면된다.


눈금 및 눈금의 레이블 조작하기

눈금, 레이블, 범례를 조작하기 위해서는 먼저 figure를 만들어야한다.
fig = plt.figure()
만든 fig에 대해서 subplot을 만든다. 간단하게 1x1 서브플롯의 1번째 axes를 지정해주자.
ax3 = fig.add_subplot(1,1,1)
여기에 랜덤한 수 1000개들의 누적합으로, 라인플롯을 하나 그려주자.
ax3.plot(np.random.randn(1000).cumsum())

image

수평축은 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] )
    image
  • 이제 x축의 눈금을 –> label(문자열)로 바꿔보자.
    원래 x의 눈금(ticks)가 5개 였으므로, tick의 수와 동일한 갯수로 좌표평면.set_xticklabels( [ "문자열1”, "문자열2” , ,,, , "문자열5” ] 로 바꿔주면된다.
    이 때, 추가적으로 회전도폰트사이즈 등을 줄 수도 있다.
    ax3.set_xticklabels( ["one", "two", "three", "four", "five"],
                         rotation = 20, fontsize = "small")
    image
  • y축 눈금도 마찬가지로 set_yticklabes ()를 이용하면된다.


다음으로, 현재 띄워놓은 axes의 제목을 변경해보자.

  • ax3.set_title("Random Walk Plot")
    image


각각 축의 눈금(tick)의 이름(label)도 넣어줄 수 있다.

  • ax3.set_xlabel("Stages")
    ax3.set_ylabel("Values")
    image



범례 조작하기


하나의 axes(좌표평면)안에 여러개의 plot들이 들어갔을 때는, 범례가 필요하다.  이를 legend라 한다.
먼저 새로운 figure와 subplot-axes를 만든 뒤 , 3개의 plot을 입혀보자.
fig = plt.figure()
ax = fig.add_subplot(1,1  ,1) : 1행1열의 subplot 1번째 좌표평면ax
image

라인플롯을 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 인자는 범례에서 나타나는 이름이다!
image
label이라는 인자로 입력된 값은, axes상에 범례를 표시할 때, 각 플롯의 이름이 된다.

  • 범례를 표시하려면,  좌표평면.legend()함수를 사용해야하며, 인자로는 loc=”best”를 주면, 최적의 위치에 범폐를 표시한다.
    ax.legend(loc="best")
    image
    loc의 인자에 따라 범례 위치를 조작할 수 있으나 생략한다.


수직, 수평 축의 범위 확인

현재 표시된 좌표평면 ax.에 get_xlim()이라는 함수를 실행하게 되면, x축 눈금의 범위가 표시된다.

image

범위를 변경하고 싶다면 set_xlim()함수를 사용한 뒤, 인자로는 리스트형태로 범위를 시작,끝 을 넣어준다.
ax.set_xlim(  [ 100,900 ])
image
수칙축(y)도 마찬가지다.
ax.set_ylim( [-100,100])
image

다 외울 수 없으니, api나 구글링 하면서 사용하는 것이 좋다.

  1. 셤기간고통중 2019.10.15 03:52

    plotting 이렇게 잘 정리된 곳 처음봤어요 ㅠㅠㅠㅠ

    너무 잘 공부하고 갑니다 정말 감사해요! :)

Matplotlib 라이브러리 사용하기

SeriesDataFrame을 plot을 만들 수 있다. 가장 많이 사용하는 것이 라인 플롯, 바 플롯, 히스토그램, 산점도 가 있다.
사용하기 앞서 %매직명령어를 사용하여 플롯 조작활성화에 대한  옵션을 주어야한다.
옵션 인자로서는 nbagg를 입력하면 plot들을 조작할 수 있는 인터렉티브한 상태가 된다. inline으로 주게 되면, 셀에서 생성된 plot에 대해 조작할 수 없다.
image
두번 째로, 필요한 패키지(라이브러리)들을 impot해야한다. matplotblit은 총 2개의 패키지를 importing한다(2번째 패키지는 산점도에 사용된다)
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
image

사용할 데이터를 생성한다. 여기서는 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 , , ] 이러한 방식으로 계산되어 열의 같은위치의 성분끼리 점점 누적되면서 더해져서 나온다.)
image

라인 플롯 사용하기

라인 플롯 : 독립변수x가 변함에 따라 종속변수y가 어떻게 변화하는지를 나타낸다.
특히 라인플롯은, [연속적인 x]에 따른 y의 변화를 살펴보는데 효과적이다.( 0, 10, 20, … , 90 : 인덱스로 주었다.)

앞에서 만든 series 변수 s를 이용해서 .plot()을 이용해 라인플롯을 그려보자.

  • image
    series s의 인덱스가 x축 / 각 성분이 y축을 구성한다. 2번째 import한 pyplot의 명령어 plt.plot (s)를 이용해도 똑같은 라인 플롯을 얻을 수 있다.
    다음 plot를 생성할 때는, 그전에 만든 plot을 파란색 전원버튼으로 꺼야한다.
  • 라인플롯을 부분 별로 살펴보자.
    x축이 인덱스/y축은 성분값을 나타낸다.
    image우측 상단의 파란색전원버튼을 누르기전까지는 마음대로 조절할 수 있다. 그리고 다음 플롯을 생성할 때는 꺼야한다.
    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))
    image
    image
    DataFrame의 인덱스가 x축을, 각 성분들이 y축을 담당하는 것은 Series와 같으나,  칼럼(열)들이 4개의 라인플롯을 만들어냈다.
    즉, 칼럼수(열 수) = 라인 플롯의 갯수다.
  • 만약 여러개의 열을 가진 df에서 특정열에 대한 1개의 라인플롯을 그리고 싶다면, 원하는 열을 Series의 형태로 뽑아지는 열 인덱싱하여서 .plot()을 호출하면된다.
    df["B"].plot()
    image

바 플롯(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"))
    image
  • 바 플롯은 라인 플롯과 동일 함수 .plot()을 호출하는데, 인자에 kind=”bar”를 주어 종류를 바꿔주면 된다.
    s2.plot(kind="bar")
    image
  • 바 플롯을 수평방향으로 줘보자. kind에 “barh”를 주면 된다.
    s2.plot(kind="barh")
    image

이제 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(칼럼이름만)을 입력하는 것을 가능하게 했다.
image

  • df2.bar( kind = “bar”)를 호출해보자. x축에는 인덱스가 있고, y축에는 값이 있고, 칼럼(열) 수 = bar plot의 수가 된다.
    image
  • DataFrame의 수평 바 플롯(kind=”barh”)를 호출하면서 추가로, 인자에 stacked=True 로 주게 되면,
    인덱스(x축)에 대한 바 플롯이 여러개 나타나는 것이 아니라
    [한 인덱스에 모든 바 플롯이 한줄로 나타나게 되어 –> 하나의 index에 대한  각 플롯들(열들)의 성분 비율 ]을 확인할 수 있게 된다.
    image


히스토그램 사용하기

히스토그램의 경우, 라인 플롯, 바 플롯과 다르게, 하나의 변수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) 형식으로 주면 된다.
image

  • 만든 Series를 이용해서 히스토그램을 만들기 위해서는  s3.hist()를 호출하면 된다.
    image
    이 때, x축을 matplotlib에서는 bin이라 부르며 인자로 줄 수 있다. 기본적으로 10칸의 bin을 히스토그램이 차지하고 있다.
    그래프를 보면 -0.5bin에 해당하는 값의 갯수는 50개가 약간 안되는 것을 알 수 있다.
  • bin의 갯수를  s3.hist(bins=50) 처럼 bins=구간의 갯수로 직접 지정해줄 수 있다.=> 갯수가 많아지면,  그만큼x가 들어있는 구간도 좁아지면서, 더 세밀하게 관찰 할 수 있다. 히스토그램의 막대 하나하나의 폭이 좋아진 것을 확인할 수 있다.
    image
  • 또, bins의 갯수뿐만 아니라, normed인자 =  True로 주면, 이전까진 구간에 속한 x의 개수였지만, <각 bin에 속하는 갯수를 전체개수로 나눈 비율, 즉, 정규화된 값>을 bar의 높이로 사용하게 된다. 애초에 200개 샘플을 정규분포에 추출하였기 때문에,  정규화된 결과는 정규분포를 의미하는 종모양과 유사하게 나타난다.
    s3.hist(bins=100, normed=True)
    image



산점도(시각적인 상관관계) 사용하기

라인 플롯이나 바 플롯의 목적은 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()로 연결 할 수 있게 한다.
image
이제, 각 정규분포에서 뽑아낸 100x1 array 2개를 concatenate()로 붙힌,
100x2 array를 DataFrame으로 만들고, 각 컬럼명을 x1, x2로 준다.
image

  • 이제 df3라는 DataFrame 의 두 칼럼 x1과 x2의 시각적인 상관관계(산점도)를 알아보기 위해, pyplot에서 제공하는 plt.scatter()함수를 호출해야한다. 이 때, 인자로 DataFrame의 각 열을 인덱싱해서 넣어주면 된다.
    plt.scatter( df3["x1"], df3["x2"])
    image
    산점도 상의 각 점들은, DataFrame의 각 행에 있는 x1, x2성분의 상관관계를 의미한다. 즉, x축이 x1  y축이 x2의 값이다.
    만약 x1과 x2의 상관관계가 양의 상관관계면 / 방향으로, 음의 상관관계면 \ 방향으로 점들이 나타날 것이다.
    위의 그림은 큰 상관관계가 없다고 볼 수 있다.
  1. Hi 2020.03.24 16:55

    33, 34실행문에서 33은 s3인데 34는 s2변수를 입력하셨어용~

    양질의 게시글 감사합니다~

참고 : pandas 분석 정리

2018. 2. 25. 16:22
=> 파일 읽고 기본적인 분석하기 :
파일읽기(.read_csv)–> shape/head-> .columns확인–> 필요한 열만 뽑아낸 df 만들기–> head/tail –> 각 열의 데이터타입 눈확인 –> 문자열 데이터 범주확인 –> 결측값(NaN) 제거 후 shape확인
=> 기준열에 특정성분 따른(term-2가지 문자열)  특정열의 합(loan_amnt 총합) 파악하기 :
기준열의 문자형데티어 범주확인 –> 빈 딕셔너리{ }생성-> 중복없는 기준열 변수 생성 –>  for문 안에서, 마스크로 중복없는 기준열의 각 성분에 따른 특정열만 뽑고, 가상으로만들어진 nx1의 총합 계산후 변수에 담기 –>  각 성분을 딕셔너리[ 열이름]으로 인덱싱하여 딕셔너리의 키값으로 생성  =  우항에 키값에 대한 총합 변수 대입으로 밸류값 생성 –> 생성된 { 키값1:밸류값1 , 키값2:밸류값2 }의 밸류값이 1개로 구성된 1xn 1차원 딕션너리를 1차원인 Series에 담기
image_thumb[14]

=> 기준열의 일부성분들을 가진 행들을 골라내어 그 행들의 특정열 분포 확인하기
헤더확인-> 범주 확인후 중복없는 기준열 변수(total category)생성 -> 일부성분만 index로 열 인덱싱한 일부성분기준열 변수 생성(bad category) -> 전체기준열에 isin( 일부성분기준열)을 통해, 마스크생성한 것을 새로운 열로 추가( df[ 새로운 마스크 열 ]) -> 행인덱스에 마스크==True를 통해 행들을 골라낸 후, 열인덱스에 특정열만 골라내어, 가사의 nx1의 Series획득한 상태에서 .value_counts()를 입혀 Series[ 특정열값 - 행 수 ]의 변수(bad_to_grade) 생성 -> Series를 오름차순 정렬 -> good category를 원하면 마스크 ==False로 구함
image_thumb[17]

상관관계 분석하기 :
기준열. corr( 특정열 )

파일쓰기 : to_csv()

파일 읽기(csv)

이전 numpy의 파일 읽는 방법을 복습해보자.
data = np.loadtxt("data/movielens-1m/ratings.dat", delimiter = "::", dtype = np.int64)
np.savetxt("mean_rating_by_user.csv", mean_rating_by_user_array, fmt='%.3f', delimiter=',')

pandas에서 파일을 읽는 방법은, pd.read_csv()함수를 사용하여 –> DataFrame으로 자동으로 가져온다.
인자에는 간단하게  “파일경로”와  구분자 sep = “,” 를 지정해준다.
파일 경로는 홈화면에서 파일을 따라간 뒤, 주소창에 있는 것을 복사해오면 간단하다.
이 때, dtype오류가 나서 마지막 인자 dtype = ‘unicode’를 붙혀줬더니 정상적으로 가져와졌다.

data = pd.read_csv("data/lending-club-loan-data/loan.csv", sep=","dtype='unicode')

image

데이터를 가져오는데 시간이 걸려서 난 오류라고한다. 무시하고 dtype안붙혀줘도 된다.
data = pd.read_csv("data/lending-club-loan-data/loan.csv", sep=",")
image


- 자료는 lengding-club에서 제공해주는 대출정보다.

DataFrame 분석해보기

  1. **** csv – > dataFrame으로 가져왔으면 처음으로 해야할 일은, shape몇 행의 데이터를 살펴보는 것이다.
    먼저 shape를 보자.  data.shape
    image
    shape를 보니, 행이 88만개 / 열이 74개 이다.
  2. 이제 열을 구체적으로  보기 위해서,  data.columns를 확인한다. 너무 많으므로, 우리에게 유의미한 것만 가져와 써야한다.
    image
    • loan_amnt: 대출자의 대출 총액
    • loan_status: 대출의 현재 상태*
    • grade: LC assigned loan grade**
    • int_rate: 대출 이자율
    • term: 대출 상품의 기간 (36-month vs. 60-month)

  3. 필요한 열만 떼어네서, 새로운 DataFrame 변수에 넣어보자. 여러 칼럼을 인덱싱할 때, 리스트의 형태로 넣어줘야한다.
    “loan_amnt”, “loan_status”, “grade”, “int_rate”, “term” 열만 가져온다.
    image
  4. shape를 파악했으면 이제 앞, 뒤 5개행만 볼 수 있는 df2.head()함수 df2.tail()함수로 확인한다.
    image
  5. 데이터를 보고, loan_amnt , int_rate에는 숫자정보 / loan_status, grade, term에는 문자열 정보가 있는 것을 확인한다.
    (shape->필요한 열만 뽑아내기–> head/tail –> 각 열의 데이터타입 눈으로 확인)
  6. 문자열 정보를 가진 열(칼럼)에 대해서 데이터가 범주형인지 아닌지  .unique() 함수로 확인해야한다.
    image
    (파일읽기–> shape-> columns확인–> 필요한 열만 뽑아내기–> head/tail –> 각 열의 데이터타입 눈확인 –> 문자열 데이터 범주확인)
  7. 결측값(NaN)을 포함한 행들 제거시키기 위해서 .dropna(how=”any”)함수를 이용한다.
    만일 NaN가 존재하여 shape로 살펴봤는데, 행의 개수가 줄어들었으면, 뽑은 것으로 새로운 df를 만들어야할 것이다.
    image
    (파일읽기–> shape-> columns확인–> 필요한 열만 뽑아내기–> head/tail –> 각 열의 데이터타입 눈확인 –> 문자열 데이터 범주확인 –> 결측값(NaN) 제거 후 shape확인)

즉, 어떤 csv파일을 받아서 DataFrame으로 받았을 때, 기본적인 분석
파일읽기–> shape/head-> .columns확인–> 필요한 열만 뽑아낸 df 만들기–> head/tail –> 각 열의 데이터타입 눈확인 –> 문자열 데이터 범주확인 –> 결측값(NaN) 제거 후 shape확인
의 과정을 거쳐야한다.



데이터분석 1 – 대출기간(term)에 따른 대출총액(loan_amnt) 파악하기
[ 기준열: 범주형 문자열데이터 – 각 성분에 따른 특정열총합 각각 구해서 담기 ] : 각 성분에 따른 것for문에 들어갈 것임

해당자료에는 term에 문자열정보로서, 36개월 대출과 60개월 대출 2가지 범주가 존재한다.
여기서 term열2가지 데이터를 기준으로 –>  전체  대출 총액(loan_amnt)을 확인해보자.
image

  1. 각 기간에 따른 대출총액의 합을 for문을 돌면서 하나씩 저장해줄 빈 딕션너리를 생성한다.(term_to_loan_amnt_dic)
  2. term 열에 대해 중복없는기준열변수로 만든다. 36개월과 60개월이 나올 것이다. (uniq_terms)
    image
  3. 중복없는 기준열인(uniq_terms)가 for문안에서 하나씩(term)로 대입되면서 for반복문을 만든다. 이 때, term은 전체기준열 안에 있는 특정성분을 의미한다.
  4. term열 중 특정성분으로 작용할 term으로 df[“term”]열에 대한 마스크를 만들고,
    그것을 df2.loc[]의 행 인덱스에 집어넣는다.(DataFrame에서 행과 렬을 동시에 인덱싱하는 loc만 가능),
    여기까지는 조건에 맞는 행들만 골라내진 상태이므로, 기준에 따라 불러오고 싶은 열을 인덱스로 준다. 즉, “loan_amnt” 열 넣어준다.
    각, 특정term을 만족시키는 행(36또는 60개월)이 여러(n)개 있고 -> 불러온 “loan_amnt” 1개의 열이므로
    nx1의 dataFrame을 이룰것이다. (Series형태다. 1차원 nx1)
    예를 들어,
                    (term-열인덱싱x 생략됨)     “loan_amnt
                  01행 –(  36 months )             3만원
                  32행 –(  36 months )             76만원
                  51행 –(  36 months )             21만원

    for문에 마스크로 행index에 집어넣어 원하는 행들을 뽑아내면 –> n행
    열index에 집어넣은 우리가 필요한 열1개면, n x 1 (Series형태)  / 2개면 n x 2 DataFrame형태
     

    이러한 n x 1  DataFrame을 .sum()함수로 모든 성분을 더하면 –> 36months에 대한 “loan_amnt”의 총액이 계산된다.
    그 값을 loan_amnt_sum 변수에 넣어준다. for문을 돌면, 36개월에 대한 대출총액 / 60개월에 대한 대출총액 2개가 담길것이다.
    image
  5. 이제 for문을 돌면서, 각 unique한 term마다, 대출총액을 모두 더한 loan_amnt_sum을
    1번에 만든 빈 딕셔너리(term _to_loan_amnt_dict)에 담을 것인데,
    각 조건성분인 term을 딕셔너리의 [열이름]형태로 열 인덱싱하여 새로운 (키값)을 생성하면서 & 동시에 윗줄에서 계산된 모든 대출총액을 더한 값들을 대입하여 키값으로 만든다. 예를 들면, 36개월에 대한 대출금액 총합을 –> 딕셔너리의 키값에 대한 벨류값으로  넣을 것이다.
    image
    (딕셔너리에다가 열인덱싱을 통해서 키값이 생성되고 = 우항이 밸류값이 된다)
    ( 10강 : 딕셔너리 { 키값 : [밸류1, 밸류2,] , 키값2 : [밸류3, 밸류4] } –>  DataFrame.columns : 키값, 키값2 , 행 갯수 : 리스트성분 수)
    for문을 돌면서 들어오는 term열의 특정성분(36 or 60)으로 하는 열인덱싱의 열이 –> { 키값 : }
    들어간 term마다 대출총액 – > {  : 밸류값} 이 될 것이다.
    image

***9강 numpy의 데이터분석에서는  빈 리스트[]를 생성하고, for문을 돌면서, 거기다가 1차원임에도 불구하고,
리스트안에 다시[ 성분1, 성분2]의 리스트를 만들고 –> 리스트안에 [  [리스, 트] ]를 만들것을 np.array로 넣기만 하였더니
M x N으로 바로 사용할 수 있었다.

  • for문을 돌면서 하나씩 받기 위해서  빈 딕셔너리를 만들고,  거기다가  { 키값 : 밸류 } 형태로 채웠던 것을,
    편하게 데이터를 관리하기 위해 1차원 딕셔너리( 1행(밸류 성분수) x n열(키값들)) –> Series에 담아준다.
    *** 딕셔너리 키값 수 = 열 수 , value리스트 수 = 행 수 였는데 지금 value값이 하나밖이므로, 행도 1개다.
    *** 행 1개 인 것이 바로, 1xn 의 1차원 Series다. 그래서 키값 = 열 수 가 아니라 1차원형태의 행 index가 된다.
    *** 즉, 다시 정리하자면,  딕셔너리 中{키값 : 밸류값}의 밸류가 1개 뿐인 것은 1차원 Series로 바꿀수 있으며, 키값=>index가 된다.
           만약 딕션너리가 키값 : [밸류1, 밸류2 .. ] 등 여러개가 되면, 키값=> / 리스트성분의 수 => 행수인 DataFrame이 된다.
            my) Series는 인덱스를 인자로 주는 장점이 있다. 즉, 열은 없다. 시리즈로 전환되는 밸류1개의 딕셔너리에서 키값= 행index
    image


=> 파일 읽고 기본적인 분석하기 :
파일읽기(.read_csv)–> shape/head-> .columns확인–> 필요한 열만 뽑아낸 df 만들기–> head/tail –> 각 열의 데이터타입 눈확인 –> 문자열 데이터 범주확인 –> 결측값(NaN) 제거 후 shape확인
=> 기준열에 특정성분 따른(term-2가지 문자열)  특정열의 합(loan_amnt 총합) 파악하기 :
기준열의 문자형데티어 범주확인 –> 빈 딕셔너리{ }생성-> 중복없는 기준열 변수 생성 –>  for문 안에서, 마스크로 중복없는 기준열의 각 성분에 따른 특정열만 뽑고, 가상으로만들어진 nx1의 총합 계산후 변수에 담기 –>  각 성분을 딕셔너리[ 열이름]으로 인덱싱하여 딕셔너리의 키값으로 생성  =  우항에 키값에 대한 총합 변수 대입으로 밸류값 생성 –> 생성된 { 키값1:밸류값1 , 키값2:밸류값2 }의 밸류값이 1개로 구성된 1xn 1차원 딕션너리를 1차원인 Series에 담기
image



데이터분석 2 – 대출상태(loan_status)중 불량상태에 해당하는 사람들의 대출등급(grade) 파악하기
[ 기준이 범주형 문자열데이터-> 일부성분만 떼내서 분류 –>  일부성분만 가진 행들에 대해 특정열 분포확인]  : 각 특정성분x-> for문 x

  1. 분석에 앞서 기본적으로, 들고 있는 DataFrame의 head() 확인
    image
  2. 문자형데이터로 구성된 기준열의 범주 확인 –> 의미상 불량상태 / 우량상태를 확인할 수 있다.
    image
  3. 기준열을 중복없는 기준열 변수 생성 (total_status_category)
    image
  4. 기준열의 범주(2)를 바탕으로 –> 중복없는기준열(1차원 array)에 index를 사용한 인덱싱으로 특성성분들(불량상태) 만들기
    (1) 불량상태 (bad_status_category)  : bad_status_category = total_status_category[ [1,3,4,5,6,8 ] ]
    image
  5. 기준열.isin( 특정성분들)을 이용해서, 불량상태에 따른 기준열의 마스크를 만들어 df2에다가 새로운 열로 추가한다.
    df2[“bad_loan_status”] 열이 추가되었으면, head()로 새로운열을 확인한다.
    df2["bad_loan_status"] = df2["loan_status"].isin(bad_status_category)
    image
    image
    기준열 중에, bad_status_category라는 특정성분을 가지고 있으면, 새로운열에는 True라고 표시가 된다.
  6. 이제 bad_loan_status 열에 True(불량상태의 loan_status)를 가진 행들에 대해  grade의 분포를 살펴보기 위해,
    for문안에서 중복없는 기준열의 특정성분으로 마스크를 비교하는 것이 아니라,  for문 없이
    (1)불량상태를 기준으로 만든 열 마스크(bad_loan_status)==True를 행 index에 집어넣어, 행들을 골라내고
    (2) 그 행들에 대한 “grade”열만 가져온다. –>  골라낸 행들 x “grade”열 의   nx1 형태의 DataFrame이 가상으로 생긴다.

  7. nx1의 dataFrame을 이룰것이다. (Series형태다. 1차원 nx1)
    예를 들어,
                        (행인덱싱의 True)     “grade
                  01행 –(  True )                 B
                  32행 –(  True  )                C
                  51행 –(  True  )                 A

    (3) 그렇게 뽑아낸 df에 value_counts() 함수를 사용하여, grade열의 각 성분들에 대한 행의 갯수가 Series의 형태로 얻어진다.
        그것을 새로운 변수 bad_loan_status_to_grades 에 담는다.
    image

  8. Series의 index(열은 없다)를 보기좋게 하기 위해, sort_index()함수로 오름차순으로 정렬해준다.
    image

  9. 대출상태가 우량상태인 경우를 뽑으려면, 불량상태와 정반대로만 하면된다. 즉, 마스크 == False로 뽑아내서 담아주면된다.

=> 기준열의 일부성분들을 가진 행들을 골라내어 그 행들의 특정열 분포 확인하기
헤더확인-> 범주 확인후 중복없는 기준열 변수(total category)생성 -> 일부성분만 index로 열 인덱싱한 일부성분기준열 변수 생성(bad category) -> 전체기준열에 isin( 일부성분기준열)을 통해, 마스크생성한 것을 새로운 열로 추가( df[ 새로운 마스크 열 ]) -> 행인덱스에 마스크==True를 통해 행들을 골라낸 후, 열인덱스에 특정열만 골라내어, 가사의 nx1의 Series획득한 상태에서 .value_counts()를 입혀 Series[ 특정열값 - 행 수 ]의 변수(bad_to_grade) 생성 -> Series를 오름차순 정렬 -> good category를 원하면 마스크 ==False로 구함
image




데이터분석 3 – 대출총액(loan_status)과 대출이자율(int_rate) 간의 상관관계 분석 [ 숫자-숫자 데이터]

분석하기 앞서, 정리된 데이터의 head()를 살펴본다.
image

  1. 기준열인 df2[“loan_amnt”]과 .corr ( 비교열  df2[“int_rate”]  )함수를 통해 상관계수를 뽑아낸다.
    image
    *** 문자형정보를 가진 열을 비교하면 먹통이 된다.

  2. 상관관계 분석하기 :
    기준열. corr( 특정열 )


파일 쓰기

파일 읽기는 : data = pd.read_csv("data/lending-club-loan-data/loan.csv", sep="," ) 였다.

bad_loan_status_to_grades 에 정리된 자료를 .to_csv()를 호출하면서
첫번째 인자에는 파일명,
두번째 인자는 구분자를 입력해준다.

bad_loan_status_to_grades.to_csv(“bad_loan_status.csv”, sep="," )
image

파일쓰기 : to_csv()

  1. YERI JO 2019.05.09 01:27

    int_rate 가 최대인 index를 찾으려면 어떻게 해야 하나요?

    • nittaku 2019.05.09 21:39 신고

      제가 이 포스트를 안본지는 오래되었지만,
      for + enumerate + 칼럼인덱싱.max() 을 사용하면 될 것 같네요~!

  2. Help 2020.07.12 12:25

    안녕하세요, 좋은 자료 감사드립니다.

    그런데 for term in uniq_terms: 부분에서,
    코드를 실행하면 Type Error: 'method' object is not iterable 이라는 오류가 나옵니다.
    차례 차례 다시해보아도 같은 오류 문구로 실행이 불가하네요.

    term 범주형 자료로 iterable한 것 같은데 뭐가 문제인지 모르겠습니다.

  3. nittaku 2020.07.12 13:59 신고

    함수명이랑 변수명이랑 잘못입력하신것은 아닌지 확인해보셔요~~!

+ Recent posts