중복된 행 제거하기

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

+ Recent posts