이번 포스팅에서는 Python의 urllib 과 BeautifulSoup 모듈을 사용해서 웹 페이지의 내용을 파싱하여 필요한 데이터만 크롤링, 스크래핑하는 방법을 소개하겠습니다. 

 

urllilb 모듈은 웹페이지 URL 을 다룰 때 사용하는 Python 라이브러리입니다. 가령, urllib.request 는 URL을 열고 읽을 때 사용하며, urllib.parse 는 URL을 파싱할 때 사용합니다. 

 

BeautifulSoup 모듈은 HTML 과 XML 파일로부터 데이터를 가져올 때 사용하는 Python 라이브러리입니다. 이 모듈은 사용자가 선호하는 파서(parser)와 잘 작동하여, parse tree 를 조회하고 검색하고 수정하는 자연스러운 방법을 제공합니다. 

 

python urllib, BeautifulSoup module for web scraping

 

 

이번 예제에서는

(1) urllib.request 의 urlopen 메소드로 https://oilprice.com/ 웹페이지에서 'lng' 라는 키워드로 검색했을 때 나오는 총 20개의 페이지를 열어서 읽은 후

(2) BeautifulSoup 모듈을 사용해 기사들의 각 페이지내에 있는 20개의 개별 기사들의 '제목(title)', '기사 게재일(timestamp)', '기사에 대한 설명 (description)' 의 데이터를 파싱하고 수집하고,

(3) 이들 데이터를 모아서 pandas DataFrame 으로 만들어보겠습니다. (총 20개 페이지 * 각 페이지별 20개 기사 = 총 400 개 기사 스크랩핑)

 

webpage crawling, scraping using python urllib, BeautifulSoup, pandas

 

아래의 예시 코드는 파송송님께서 짜신 것이구요, 각 검색 페이지에 20개씩의 기사가 있는데 제일 위에 1개만 크롤링이 되는 문제를 해결하는 방법을 문의해주셔서, 그 문제를 해결한 후의 코드입니다.

 

##-- How to Scrape Data on the Web with BeautifulSoup and urllib

from bs4 import BeautifulSoup
from urllib.request import urlopen
import pandas as pd
from datetime import datetime

col_name = ['title', 'timestamp', 'descrip']
df_lng = pd.DataFrame(columns = col_name)

for j in range(20):
    ## open and read web page
    url = 'https://oilprice.com/search/tab/articles/lng/Page-' + str(j+1) + '.html'
    with urlopen(url) as response:
        soup = BeautifulSoup(response, 'html.parser')
        headlines = soup.find_all(
        	'div', 
        	{'id':'search-results-articles'}
        	)[0]
        
        ## getting all 20 titles, timestamps, descriptions on each page
        title = headlines.find_all('a')
        timestamp = headlines.find_all(
        	'div', 
        	{'class':'dateadded'}
            )
        descrip = headlines.find_all('p')
        
        
        ## getting data from each article in a page
        for i in range(len(title)):
            title_i = title[i].text
            timestamp_i = timestamp[i].text
            descrip_i = descrip[i].text

            # appending to DataFrame
            df_lng = df_lng.append({
            	'title': title_i, 
                'timestamp': timestamp_i, 
                'descrip': descrip_i}, 
                ignore_index=True)

        if j%10 == 0:
            print(str(datetime.now()) + " now processing : j = " + str(j))

# remove temp variables
del [col_name, url, response, title, title_i, timestamp, timestamp_i, descrip, descrip_i, i, j]

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅에서는 Python pandas DataFrame의 칼럼 이름을 바꾸는 방법 (https://rfriend.tistory.com/468)을 소개하였습니다. 

 

이번 포스팅에서는 pandas DataFrame의 칼럼 순서를 바꾸는 방법(how to change the order of pandas DataFrame columns?)을 소개하겠습니다. 

 

how to change the order of pandas DataFrame columns

 

 

먼저 6개의 칼럼을 가진 간단한 pandas DataFrame 을 만들어보겠습니다. 

 

import numpy as np
import pandas as pd

# making the sample DataFrame
df = pd.DataFrame(np.arange(12).reshape(2, 6), 
                 columns=['x1', 'x2', 'x3', 'x4', 'x5', 'y'])
                 

print(df)

#    x1  x2  x3  x4  x5   y
# 0   0   1   2   3   4   5
# 1   6   7   8   9  10  11

 

 

 

위의 예제 DataFrame 'df'에서 마지막에 있는 칼럼 'y'를 제일 앞 위치로 변경을 해보겠습니다. 

 

 

(1) 새로운 순서의 칼럼 이름 리스트로 인덱싱 해와서 새로운 DataFrame 만들기

 

# (1) reassigning the new order of columns
df2 = df[['y', 'x1', 'x2', 'x3', 'x4', 'x5']]

print(df2)

#     y  x1  x2  x3  x4  x5
# 0   5   0   1   2   3   4
# 1  11   6   7   8   9  10

 

 

 

(2) 칼럼 리스트의 마지막 위치(-1)로 인덱싱해서 새로운 순서의 칼럼 리스트 만든 후, 새로운 DataFrame 만들기

 

 

# (2) reassignning with a list of columns
col_list = df.columns.tolist()

print(col_list)
#['x1', 'x2', 'x3', 'x4', 'x5', 'y']


new_col_list = col_list[-1:] + col_list[:-1]

print(new_col_list)
#['y', 'id', 'x1', 'x2', 'x3', 'x4', 'x5']


df3 = df[new_col_list]

print(df3)

#     y  x1  x2  x3  x4  x5
# 0   5   0   1   2   3   4
# 1  11   6   7   8   9  10

 

 

 

(3) 문자와 숫자로 구성된 여러개의 칼럼 이름을 for loop 순환문으로 만들어서 새로운 순서의 칼럼 리스트를 만든 후, 새로운 DataFrame 만들기

 

## (3) making columns list using concatenation and for-loop
new_col_list2 = ['y'] + ['x'+str(i+1) for i in range(5)]

print(new_col_list2)
#['y', 'x1', 'x2', 'x3', 'x4', 'x5']

print(df[new_col_list2])

#     y  x1  x2  x3  x4  x5
# 0   5   0   1   2   3   4
# 1  11   6   7   8   9  10

 

 

 

(4) 숫자 위치 인덱스를 사용해서 새로운 순서로 칼럼을 인덱싱해와서, 새로운 DataFrame 만들기

 

# (4) reassignning the order using Positioning index
df4 = df.iloc[:, [5, 0, 1, 2, 3, 4]]

print(df4)

#     y  x1  x2  x3  x4  x5
# 0   5   0   1   2   3   4
# 1  11   6   7   8   9  10

 

 

 

(5) 칼럼 이름이 'y' 이 것과 'y'가 아닌 조건문(if condition statement)으로 새로운 순서의 칼럼 라스트를 만들어서, 새로운 DataFrame 만들기

 

# (5) reassignning the column order using condition statement 
df5 = df[['y'] + [col for col in df.columns if col != 'y']]

print(df5)

#     y  x1  x2  x3  x4  x5
# 0   5   0   1   2   3   4
# 1  11   6   7   8   9  10

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

Python pandas의 DataFrame은 특이하고 재미있게도 두 개 이상의 MultiIndex Column 을 가지는 DataFrame 도 지원합니다. R의 DataFrame 이나 DB의 Table 에서는 칼럼이라고 하면 당연히 1개 layer의 칼럼들만을 지원하기에, Python pandas의 2개 이상 layer의 칼럼들을 가진 DataFrame 이 처음에는 생소하고 재미있게 보였습니다. 

 

이번 포스팅에서는 Python pandas DataFrame 중에서 두 개의 MultiIndex Column 을 가지는 DataFrame 에 대해서, 이중 한개 layer의 칼럼을 기준으로 Stacking을 해서 wide-format 을 long-format으로 DataFrame을 재구조화 해보겠습니다.

 

 

reshaping pandas DataFrame with MultiIndex Column by Stacking

 

(1) csv 파일을 읽어와서 MultiIndex Column을 가진 pandas DataFrame 으로 만들기

(2) MultiIndex Column 의 Level에 이름 부여하기

(3) MultiIndex Column DataFrame의 특정 Level을 Stacking 해서 Long-format 으로 재구조화 하기

(4) 재구조화한 DataFrame의 Index 를 칼럼으로 재설정하고 정렬하기

 

 

위의 순서대로 간단한 샘플 데이터를 가지고 예를 들어보겠습니다. 

 

 

(1) csv 파일을 읽어와서 MultiIndex Column을 가진 pandas DataFrame 으로 만들기

 

아래에 첨부한 dataset.csv 파일을  pandas 의 read_csv() 메소드로 csv 파일을 읽어와서 DataFrame으로 만들 때 header=[0, 1] 옵션을 지정해줌으로써 ==> 첫번째와 두번째 행을 MultiIndex Column 으로 해서 DataFrame을 만들 수 있습니다. 

 

dataset.csv
0.00MB

## 참고로, dataset.csv 파일에는 아래 샘플 데이터가 들어있습니다.

g1,g1,g1,g2,g2,g2
c1,c2,c3,c1,c2,c3
1,2,3,4,5,6
7,8,9,10,11,12
13,14,15,16,17,18

import pandas as pd

## reading csv file with multi-column index
df = pd.read_csv('dataset.csv', header=[0, 1])

print(df)

#    g1          g2        
#    c1  c2  c3  c1  c2  c3
# 0   1   2   3   4   5   6
# 1   7   8   9  10  11  12
# 2  13  14  15  16  17  18


## MultiIndex Columns
df.columns

# MultiIndex([('g1', 'c1'),
#             ('g1', 'c2'),
#             ('g1', 'c3'),
#             ('g2', 'c1'),
#             ('g2', 'c2'),
#             ('g2', 'c3')],
#            )

 

 

 

(2) MultiIndex Column 의 Level에 이름 부여하기

 

MultiIndex Column의 Level 에 접근할 때 "위치(position)"로 할 수도 있고, 혹은 Level에 이름을 부여(rename)해서 "이름(name)"을 기준으로 접근할 수도 있습니다. 

 

rename() 메소드를 사용해서 이번 예제의 MultiIndex Column의 첫번째 Level에는 'grp_nm' 이라는 이름을, 두번째 Level 에는 'col_nm' 이라는 이름을 부여해보겠습니다. 

 

## renaming multi-column index
df.columns.rename(['grp_nm', 'col_nm'], inplace=True)


print(df)

# grp_nm  g1          g2        
# col_nm  c1  c2  c3  c1  c2  c3
# 0        1   2   3   4   5   6
# 1        7   8   9  10  11  12
# 2       13  14  15  16  17  18


df.columns

# MultiIndex([('g1', 'c1'),
#             ('g1', 'c2'),
#             ('g1', 'c3'),
#             ('g2', 'c1'),
#             ('g2', 'c2'),
#             ('g2', 'c3')],
#            names=['grp_nm', 'col_nm'])  # <== now, names of MultiIndex levels

 

 

 

(3) MultiIndex Column DataFrame의 특정 Level을 Stacking 해서 Long-format 으로 재구조화 하기

 

위의 (2)번에서 MultiIndex Column의 첫번째 Level 에 'grp_nm' 이라는 이름을 부여했는데요, 이번에는 'grp_nm' 이름의 Level 을 기준으로 stack() 메소드를 사용해서 wide-format 을 long-format 의 DataFrame으로 재구조화(reshaping) 해보겠습니다. 

 

이렇게 stacking을 해서 재구조화하면 MultiIndex Column DataFrame 이 (우리가 익숙하게 사용하는) SingleIndex Column의 DataFrame으로 바뀌게 되며, ==> 이제 'grp_nm'은 Index 로 들어가 있습니다. 

 

## reshaping DataFrame from MultiIndex Column from SingleIndex long-format by stacking

df_stacked = df.stack(level='grp_nm')

print(df_stacked)

# col_nm    c1  c2  c3
#   grp_nm            
# 0 g1       1   2   3
#   g2       4   5   6
# 1 g1       7   8   9
#   g2      10  11  12
# 2 g1      13  14  15
#   g2      16  17  18

 

 

 

(4) 재구조화한 DataFrame의 Index 를 칼럼으로 재설정하고 정렬하기

 

마지막으로, MultiIndex Column 의 첫번째 Level 을 Stacking 하고 난 후 Index로 사용된 'grp_nm' 을 reset_index() 메소드를 사용해서 칼럼으로 재설정하고, ==> 가시성을 높일 수 있도록 sort_values(by=['grp_nm']) 을 사용해서 'grp_nm' 칼럼을 기준으로 오름차순 정렬을 해보겠습니다. 

 

## reset index and sorting by column
df_stacked_sorted = df.stack(level='grp_nm').reset_index(['grp_nm']).sort_values(by=['grp_nm'])


print(df_stacked_sorted)

# col_nm grp_nm  c1  c2  c3
# 0          g1   1   2   3
# 1          g1   7   8   9
# 2          g1  13  14  15
# 0          g2   4   5   6
# 1          g2  10  11  12
# 2          g2  16  17  18

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅의  rfriend.tistory.com/262 에서는 Python pandas DataFrame의 결측값을 fillna() 메소드를 사용해서 특정 값으로 채우거나 평균으로 대체하는 방법을 소개하였습니다. 

 

이번 포스팅에서는 Python pandas DataFrame 의 결측값을 선형회귀모형(linear regression model) 을  사용하여 예측/추정하여 채워넣는 방법을 소개하겠습니다. (물론, 아래의 동일한 방법을 사용하여 선형회귀모형 말고 다른 통계, 기계학습 모형을 사용하여 예측/추정한 값으로 대체할 수 있습니다.)

 

(1) 결측값을 제외한 데이터로부터 선형회귀모형 훈련하기

    (training, fitting a linear regression model using non-missing values)

(2) 선형회귀모형으로 부터 추정값 계산하기 (prediction using linear regression model)

(3) pandas 의 fillna() 메소드 또는  numpy의  np.where()  메소드를 사용해서 결측값인 경우 선형회귀모형 추정값으로 대체하기 (filling missing values using the predicted values by linear regression model)

 

fill missing values of pandas DataFrame using predicted values by machine learning model

 

아래에는 예제로 사용할 데이터로 전복(abalone) 공개 데이터셋을 읽어와서 1행~3행의 'whole_weight' 칼럼 값을 결측값(NA) 으로 변환해주었습니다. 

import pandas as pd
import numpy as np

# read abalone dataset from website
abalone = pd.read_csv("http://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data", 
                 header=None, 
                 names=['sex', 'length', 'diameter', 'height', 
                          'whole_weight', 'shucked_weight', 
                          'viscera_weight', 'shell_weight', 'rings'])
                          

# get 10 observations as an example
df = abalone.copy()[:10]


# check missing values : no missing value at all
pd.isnull(df).sum()
# sex               0
# length            0
# diameter          0
# height            0
# whole_weight      0
# shucked_weight    0
# viscera_weight    0
# shell_weight      0
# rings             0
# dtype: int64


# insert NA values as an example
df.loc[0:2, 'whole_weight'] = np.nan

df
# sex	length	diameter	height	whole_weight	shucked_weight	viscera_weight	shell_weight	rings
# 0	M	0.455	0.365	0.095	NaN	0.2245	0.1010	0.150	15
# 1	M	0.350	0.265	0.090	NaN	0.0995	0.0485	0.070	7
# 2	F	0.530	0.420	0.135	NaN	0.2565	0.1415	0.210	9
# 3	M	0.440	0.365	0.125	0.5160	0.2155	0.1140	0.155	10
# 4	I	0.330	0.255	0.080	0.2050	0.0895	0.0395	0.055	7
# 5	I	0.425	0.300	0.095	0.3515	0.1410	0.0775	0.120	8
# 6	F	0.530	0.415	0.150	0.7775	0.2370	0.1415	0.330	20
# 7	F	0.545	0.425	0.125	0.7680	0.2940	0.1495	0.260	16
# 8	M	0.475	0.370	0.125	0.5095	0.2165	0.1125	0.165	9
# 9	F	0.550	0.440	0.150	0.8945	0.3145	0.1510	0.320	19

 

 

 

(1) 결측값을 제외한 데이터로부터 선형회귀모형 훈련하기  (training, fitting a linear regression model using non-missing values)

 

 pandas 패키지의 dropna() 메소드를 이용해서 결측값이 포함된 행을 제거한 후의 설명변수  ' diameter', 'height', 'shell_weight' 를 'X' DataFrame 객체로 만들고, ' whole_weight' 를 종속변수  'y' Series로 만든 후에,  sklearn의  linear_model.LinearRegression() 메소드로   lin_reg.fit(X, y) 로 선형회귀모형을 적합하였습니다. 

 

# initiate sklearn's linear regression
from sklearn import linear_model

lin_reg = linear_model.LinearRegression()


# X and y after excluding missing values
X = df.dropna(axis=0)[['diameter', 'height', 'shell_weight']] 
y = df.dropna(axis=0)['whole_weight'] 


# fitting linear regression model using non-missing values
lin_reg_model = lin_reg.fit(X, y)

 

 

 

(2) 선형회귀모형으로 부터 추정값 계산하기 (prediction using linear regression model)

 

위의 (1)번에서 적합한 모델에 predict() 함수를 사용해서  'whole_weight'  의 값을 추정하였습니다. 

 

# Prediction
y_pred = lin_reg_model.predict(df.loc[:, ['diameter', 'height', 'shell_weight']])

y_pred
# array([0.54856977, 0.21868994, 0.69091523, 0.50734984, 0.19206521,
#        0.35618402, 0.80347213, 0.7804138 , 0.53164895, 0.85086606])

 

 

 

(3) pandas 의 fillna() 메소드 또는  numpy의  np.where()  메소드를 사용해서 결측값인 경우 선형회귀모형 추정값으로 대체하기 (filling missing values using the predicted values by linear regression model)

 

(방법 1)  pandas  의  fillna()  메소드를 사용해서  'whole_weight' 값이 결측값인 경우에는  위의 (2)번에서 선형회귀모형을 이용해 추정한 값으로 대체를 합니다. 이때  'y_pred' 는  2D numpy array 형태이므로, 이를 flatten() 메소드를 사용해서  1D array 로 바꾸어주고, 이를  pd.Series() 메소드를 사용해서 Series 데이터 유형으로 변환을 해주었습니다.   inplace=True 옵션을 사용해서 df DataFrame 내에서 결측값이 선형회귀모형 추정값으로 대체되고 나서 저장되도록 하였습니다. 

 

(방법 2)  numpy의 where() 메소드를 사용해서,  결측값인 경우  (즉,  isnull() 이 True)  pd.Series(y_pred.flatten()) 값을 가져옥, 결측값이 아닌 경우 기존 값을 가져와서  'whole_weight' 에 값을 할당하도록 하였습니다. 

 

(방법 3) for loop 을 돌면서 매 행의  'whole_weight' 값이 결측값인지 여부를 확인 후,  만약  결측값이면 (isnull() 이 True 이면) 위의 (1)에서 적합된 회귀모형에 X값들을 넣어줘서 예측을 해서 결측값을 채워넣는 사용자 정의함수를 만들고 이를  apply() 함수로 적용하는 방법도 생각해볼 수는 있으나, 데이터 크기가 큰 경우  for loop 연산은 위의 (방법 1), (방법 2) 의   vectorized operation 대비 성능이 많이 뒤떨어지므로 소개는 생략합니다. 

 

## filling missing values using predicted values by a linear regression model

## -- (방법 1) pd.fillna() methods
df['whole_weight'].fillna(pd.Series(y_pred.flatten()), inplace=True)


## -- (방법 2) np.where()
df['whole_weight'] = np.where(df['whole_weight'].isnull(), 
                              pd.Series(y_pred.flatten()), 
                              df['whole_weight'])
                              
## results
df
# sex	length	diameter	height	whole_weight	shucked_weight	viscera_weight	shell_weight	rings
# 0	M	0.455	0.365	0.095	0.548570	0.2245	0.1010	0.150	15
# 1	M	0.350	0.265	0.090	0.218690	0.0995	0.0485	0.070	7
# 2	F	0.530	0.420	0.135	0.690915	0.2565	0.1415	0.210	9
# 3	M	0.440	0.365	0.125	0.516000	0.2155	0.1140	0.155	10
# 4	I	0.330	0.255	0.080	0.205000	0.0895	0.0395	0.055	7
# 5	I	0.425	0.300	0.095	0.351500	0.1410	0.0775	0.120	8
# 6	F	0.530	0.415	0.150	0.777500	0.2370	0.1415	0.330	20
# 7	F	0.545	0.425	0.125	0.768000	0.2940	0.1495	0.260	16
# 8	M	0.475	0.370	0.125	0.509500	0.2165	0.1125	0.165	9
# 9	F	0.550	0.440	0.150	0.894500	0.3145	0.1510	0.320	19

 

 

많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 pandas DataFrame 의 칼럼 관련한 소소한 팁들을 정리해보았습니다. 

 

1. pandas DataFrame 의 칼럼 이름 확인 하기 : df.columns

2. pandas DataFrame 에 특정 칼럼 포함 여부 확인하기 : 'col' in df.columns

3. pandas DataFrame 에서 특정 칼럼 선택하기

4. pandas DataFrame 에서 특정 칼럼 제외하기

5. pandas DataFrame 칼럼 이름 바꾸기

 

 

pandas DataFrame column name list, check, difference, rename

 

먼저, 예제로 사용할 간단한 pandas DataFrame을 만들어보겠습니다. 

 

import pandas as pd

df = pd.DataFrame({'x1': [0.1, 0.1, 0.2, 0.2]
                   , 'x2': [10, 20, 30, 80]
                   , 'x3': [0.1, 0.3, 0.2, 0.6]
                   , 'y': [1, 2, 3, 10]}, 
                 index = [1, 2, 3, 4])
                 
df
[Out]:
    x1  x2   x3   y
1  0.1  10  0.1   1
2  0.1  20  0.3   2
3  0.2  30  0.2   3
4  0.2  80  0.6  10

 

 

1. pandas DataFrame 의 칼럼 이름 확인 하기 : df.columns

# DataFrame의 칼럼 이름 확인하기
df.columns

[Out]: Index(['x1', 'x2', 'x3', 'y'], dtype='object')

 

 

 

2. pandas DataFrame 에 특정 칼럼 포함 여부 확인하기 : 'col' in df.columns

 

'column_name' in df.columns 구문 형식으로 특정 칼럼이 포함되어있는지 여부를 True, False 의 boolean 형식으로 반환받을 수 있습니다.  이를 if 조건문과 함께 사용해서 특정 칼럼의 포함 여부에 따라 분기문을 만들 수 있습니다. 

# DataFrame의 칼럼 포함 여부 확인하기
'x1' in df.columns
[Out]: True


'x5' in df.columns
[Out]: False


if 'x1' in df.columns:
    print('column x1 is in df DataFrame')
[Out]: column x1 is in df DataFrame

 

 

 

3. pandas DataFrame 에서 특정 칼럼 선택하기

 

# 특정 칼럼 선택하기
y = df['y']

y
[Out]: 
1     1
2     2
3     3
4    10
Name: y, dtype: int64


# 여러개 칼럼 선택하기
X = df[['x1', 'x2', 'x3']]
X
[Out]:
        x1	x2	x3
1	0.1	10	0.1
2	0.1	20	0.3
3	0.2	30	0.2
4	0.2	80	0.6

 

 

 

4. pandas DataFrame 에서 특정 칼럼 제외하기

 

# 특정 칼럼 제외하고 나머지 칼럼 선택하기
X2 = df[df.columns.difference(['y'])]

X2
[Out]:
        x1	x2	x3
1	0.1	10	0.1
2	0.1	20	0.3
3	0.2	30	0.2
4	0.2	80	0.6

 

 

5. pandas DataFrame 칼럼 이름 바꾸기

 

# 칼럼 이름 바꾸기
X.columns = ['v1', 'v2', 'v3']

X
[Out]:

        v1	v2	v3
1	0.1	10	0.1
2	0.1	20	0.3
3	0.2	30	0.2
4	0.2	80	0.6


# 특정 칼럼만 선택해서 이름 바꾸기
X3 = X.rename(columns = {'v1': 'c1'})

X3
[Out]:
        c1	v2	v3
1	0.1	10	0.1
2	0.1	20	0.3
3	0.2	30	0.2
4	0.2	80	0.6

 

 

많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요! :-)

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅에서는 numpy 배열의 원소 값을 사전(dictionary)의 (Key, Value)를 매핑해서 변환하는 방법을 소개하였습니다. (rfriend.tistory.com/620)

 

이번 포스팅에서는 Python numpy 의 array 배열의 순서대로 정수를 사전의 키(Key)로 하고, 배열 값을 사전의 값(Value)으로 하는 Python 사전(dictionary) 으로 변환하는 몇 가지 방법을 소개하겠습니다.

 

(1) dict() 와 enumerate() 함수를 이용해 배열로 부터 사전 만들기

(2) for loop 과 enumerate() 함수를 이용해 배열로 부터 사전 만들기

 

 

 

(1) dict() 와 enumerate() 함수를 이용해 배열로 부터 사전 만들기

 

먼저, numpy 라이브러리를 불러오고, 예제로 사용할 (5, 0) shape 의 numpy array 배열을 하나 만들어보겠습니다.

 

import numpy as np

cls_weight = np.array([0.30, 0.50, 0.10, 0.03, 0.07])
cls_weight
[Out]
array([0.3 , 0.5 , 0.1 , 0.03, 0.07])

cls_weight.shape
[Out] 
(5,)

 

 

위의 'cls_weight' 배열을 사전(dictionary)으로 변환해보겠습니다. 사전(dict) 키(Key)가 '0' 부터 시작하고, 배열의 순서대로 사전의 키가 하나씩 증가하며, 배열의 순서대로 사전에 값을 할당하여 보겠습니다.  dict() 함수는 객체를 '키(Key) : 값(Value)' 의 쌍을 가지는 사전형 자료구조를 만들어줍니다.

 

## converting numpy array to dictionary, 
## dict key is starting from 0
cls_weight_dict_from_0 = dict(enumerate(cls_weight))

cls_weight_dict_from_0
[Out]
{0: 0.3, 1: 0.5, 2: 0.1, 3: 0.03, 4: 0.07}

 

 

이때 dict() 안의 enumerate() 메소드는 객체를 순환할 때 회수를 세어주는 counter 를 같이 생성해서 enumerate 객체를 반환합니다. for loop 으로 enumerate 객체를 순환하면서 counter 와 배열 내 값을 차례대로 출력을 해보면 아래와 같습니다.

## enumerate() method adds a counter to an iterable 
## and returns it in a form of enumerate object
for i, j in enumerate(cls_weight):
    print(i, ':', j)
    
[Out]
0 : 0.3
1 : 0.5
2 : 0.1
3 : 0.03
4 : 0.07

 

 

경우에 따라서는 배열의 값으로 사전을 만들었을 때, 사전의 키 값이 '0'이 아니라 '1'이나 혹은 다른 숫자로 부터 시작하는 것을 원할 수도 있습니다. 이럴 경우 enumerate(iterable_object, 1) 처럼 원하는 숫자(아래 예에서는 '1')를 추가해주면 그 값이 더해져서 counter 가 생성이 됩니다.

 

## converting numpy array to dictionary, 
## dict key is starting from 1

cls_weight_dict_from_1 = dict(enumerate(cls_weight, 1))

cls_weight_dict_from_1
[Out]
{1: 0.3, 2: 0.5, 3: 0.1, 4: 0.03, 5: 0.07}

 

 

만약 사전(dictionary)으로 변환하려고 하는 numpy array의 axis 1의 축이 있다면 flatten() 메소드를 사용해서 axis 0 만 있는 배열로 먼저 평평하게 펴준 (axis 1 축을 없앰) 후에 위의 dict(enumerate()) 를 똑같이 사용해주면 됩니다.  아래 예는 shape (5, 1) 의 배열을 flatten() 메소드를 써서 shape (5, 0) 으로 바꿔준 후에 dict(enumerate()) 로 배열을 사전으로 변환해주었습니다.

 

## array with axis1
cls_weight_2 = np.array([[0.30], [0.50], [0.10], [0.03], [0.07]])
cls_weight_2
[Out]
array([[0.3 ],
       [0.5 ],
       [0.1 ],
       [0.03],
       [0.07]])


cls_weight_2.shape
[Out]
(5, 1)


## use flatten() method to convert shape (5, 1) to (5, 0)
cls_weight_dict_2 = dict(enumerate(cls_weight_2.flatten()))
print(cls_weight_dict_2)
[Out]
{0: 0.3, 1: 0.5, 2: 0.1, 3: 0.03, 4: 0.07}

 

 

 

(2) for loop 과 enumerate() 함수를 이용해 배열로 부터 사전 만들기

 

이번에는 for loop 과 enumerate() 메소드를 같이 이용하는 방법입니다. 위의 (1) 번 대비 좀 복잡한 느낌이 있기는 하지만, (1) 번 대비 (2) 방법은 for loop 안의 코드 블럭에 좀더 자유롭게 원하는 복잡한 로직을 녹여서 사전(dictionary)을 구성할 수 있다는 장점이 있습니다.

 

아래 예에서는 (a) 먼저 cls_weight_dict_3 = {} 로 비어있는 사전을 만들어 놓고, (b) for loop 으로 순환 반복을 하면서 enumerate(cls_weight) 가 반환해주는 (counter, 배열값) 로 부터 counter 정수 숫자를 받아서 cls_weight_dict_3 의 키(Key) 로 할당해주고, 배열의 값을 사전의 해당 키에 할당해주는 방식입니다.  사전의 키에 값 할당(assinging Value to dict by mapping Key)은 Dict[Key] = Value 구문으로 해줍니다.

 

cls_weight = np.array([0.30, 0.50, 0.10, 0.03, 0.07])
cls_weight
[Out]
array([0.3 , 0.5 , 0.1 , 0.03, 0.07])

## Converting a numpy array to a dictionary
## Dict key is starting from 0
cls_weight_dict_3 = {}

for i, c_w in enumerate(cls_weight):
    cls_weight_dict_3[i] = c_w
    

print(cls_weight_dict_3)
[Out]
{0: 0.3, 1: 0.5, 2: 0.1, 3: 0.03, 4: 0.07}

 

 

사전의 키를 '0' 이 아니라 '1'부터 시작하게 하려면 enumerate()의 counter가 0부터 시작하므로, counter를 사전의 키에 할당할 때 'counter+1' 을 해주면 됩니다.

 

## converting a numpy array to a dictionary using for loop
## dict key is strating from 1

## null dict
cls_weight_dict_3_from_1 = {}

## assigning values by keys + 1
for i, c_w in enumerate(cls_weight):
    cls_weight_dict_3_from_1[i+1] = c_w
    
    
print(cls_weight_dict_3_from_1)
[Out]
{1: 0.3, 2: 0.5, 3: 0.1, 4: 0.03, 5: 0.07}

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요! :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 1차원 배열 내 고유한 원소 집합 (a set with unique elements) 을 찾고, 더 나아가서 고유한 원소별 개수(counts per unique elements)도 세어보고, 원소 개수를 기준으로 정렬(sorting)도 해보는 여러가지 방법을 소개하겠습니다.

 

 

(1) numpy 1D 배열 안에서 고유한 원소 집합 찾기
    (finding a set with unique elements in 1D numpy array)

(2) numpy 1D 배열 안에서 고유한 원소 별로 개수 구하기
    (counts per unique elements in 1D numpy array)

(3) numpy 1D 배열 안에서 고유한 원소(key) 별 개수(value)를 사전형으로 만들기

    (making a dictionary with unique sets and counts of 1D numpy array)

(4) numpy 1D 배열의 고유한 원소(key) 별 개수(value)의 사전을 정렬하기

    (sorting a dictionary with unique sets and counts of 1D numpy array)

(5) numpy 1D 배열을 pandas Series 로 변환해서 고유한 원소 별 개수 구하고 정렬하기

    (converting 1D array to pandas Series, and value_counts(), sort_values())

(6) numpy 1D 배열을 pandas DataFrame으로 변환해 고유 원소별 개수 구하고 정렬하기

    (converting 1D array to pandas DataFrame, and value_counts(), sort_values())

 

 

 

 

먼저, 예제로 사용할 간단한 numpy 1D 배열을 만들어보겠습니다.

 

## simple 1D numpy array

import numpy as np

arr = np.array(['a', 'c', 'c', 'b', 'a', 
                'b', 'b', 'c', 'a', 'c', 
                'b', 'a', 'a', 'a', 'c'])
                
                
arr
[Out] array(['a', 'c', 'c', 'b', 'a', 'b', 'b', 'c', 'a', 'c', 
             'b', 'a', 'a', 'a', 'c'], dtype='<U1')
             

 

 

(1) numpy 1D 배열 안에서 고유한 원소 집합 찾기
    (finding a set with unique elements in 1D numpy array)

 

np.unique() 메소드를 사용하면 numpy 배열 내 고유한 원소(unique elements)의 집합을 찾을 수 있습니다.

 

## np.unique(): Find the unique elements of an array
np.unique(arr)
[Out] 
array(['a', 'b', 'c'], dtype='<U1')

 

 

더 나아가서, return_inverse=True 매개변수를 설정해주면, 아래의 예처럼 numpy 배열 내 고유한 원소의 집합 배열과 함께 '고유한 원소 집합 배열의 indices 의 배열' 을 추가로 반환해줍니다.

따라서 이 기능을 이용하면 array(['a', 'c', 'c', 'b', 'a', 'b', 'b', 'c', 'a', 'c', 'b', 'a', 'a', 'a', 'c']) 를 ==> array([0, 2, 2, 1, 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 2]) 로 쉽게 변환할 수 있습니다.

 

## return_inverse=True: If True, also return the indices of the unique array
np.unique(arr, 
          return_inverse=True)
[Out]
(array(['a', 'b', 'c'], dtype='<U1'),
 array([0, 2, 2, 1, 0, 1, 1, 2, 0, 2, 1, 0, 0, 0, 2]))
 

 

 

 

(2) numpy 1D 배열 안에서 고유한 원소 별로 개수 구하기
    (counts per unique elements in 1D numpy array)

 

위의 (1)번에서 np.unique() 로 numpy 배열 내 고유한 원소의 집합을 찾았다면, return_counts = True 매개변수를 설정해주면 각 고유한 원소별로 개수를 구해서 배열로 반환할 수 있습니다.

 

## return_counts: If True, also return the number of times each unique item appears in ar.
np.unique(arr, 
          return_counts = True)     

[Out]
(array(['a', 'b', 'c'], dtype='<U1'), array([6, 4, 5]))

 

 

 

(3) numpy 1D 배열 안에서 고유한 원소(key) 별 개수(value)를 사전형으로 만들기

    (making a dictionary with unique sets and counts of 1D numpy array)

 

위의 (2)번에서 각 고유한 원소별 개수를 구해봤는데요, 이를 파이썬의 키:값 쌍 (key: value pair) 형태의 사전(dictionary) 객체로 만들어보겠습니다.

 

먼저 np.unique(arr, return_counts = True) 의 결과를 unique, counts 라는 이름의 array로 할당을 받고, 이를 zip(unique, counts) 으로 쌍(pair)을 만들어준 다음에, dict() 를 사용해서 사전형으로 변환해주었습니다.

 

## making a dictionary with unique elements and counts of 1D array
unique, counts = np.unique(arr, return_counts = True)
uniq_cnt_dict = dict(zip(unique, counts))

uniq_cnt_dict
[Out]
{'a': 6, 'b': 4, 'c': 5}

 

 

 

(4) numpy 1D 배열의 고유한 원소(key) 별 개수(value)의 사전을 정렬하기

    (sorting a dictionary with unique sets and counts of 1D numpy array)

 

위의 (3)번까지 잘 진행을 하셨다면 이제 (unique : counts) 쌍의 사전을 'counts' 의 값을 기준으로 오름차순 정렬(sorting a dict by value in ascending order) 또는 내림차순 정렬 (sorting a dict by value in descending order) 하고 싶은 마음이 생길 수 있는데요, 이럴 경우 sorted() 메소드를 사용하면 되겠습니다. (pytho dictionary 정렬 참조: rfriend.tistory.com/473)

 

## sorting a dictionary by value in ascending order
## -- reference: https://rfriend.tistory.com/473
sorted(uniq_cnt_dict.items(), 
       key = lambda x: x[1])
       
[Out]
[('b', 4), ('c', 5), ('a', 6)]


## sorting a dictionary by value in descending order
sorted(uniq_cnt_dict.items(), 
       reverse = True, 
       key = lambda x: x[1])
       
[Out]
[('a', 6), ('c', 5), ('b', 4)]

 

 

 

(5) numpy 1D 배열을 pandas Series 로 변환해 고유한 원소별 개수 구하고 정렬하기

    (converting 1D array to pandas Series, and value_counts(), sort_values())

 

pandas 의 Series 나 DataFrame으로 변환해서 데이터 분석 하는 것이 더 익숙하거나 편리한 상황에서는 pandas.Series(array) 나 pandas.DataFrame(array) 로 변환을 해서, value_count() 메소드로 원소의 개수를 세거나, sort_values() 메소드로 값을 기준으로 정렬을 할 수 있습니다.

 

import pandas as pd

## converting an array to pandas Series
arr_s = pd.Series(arr)
arr_s
[Out]
0     a
1     c
2     c
3     b
4     a
5     b
6     b
7     c
8     a
9     c
10    b
11    a
12    a
13    a
14    c
dtype: object


## counting values by unique elements of pandas Series
arr_s.value_counts()
[Out]
a    6
c    5
b    4
dtype: int64


## sorting by values in ascending order of pandas Series
arr_s.value_counts().sort_values(ascending=True)
[Out]
b    4
c    5
a    6
dtype: int64

 

 

(6) numpy 1D 배열을 pandas DataFrame으로 변환해 고유한 원소별 개수 구하고 정렬하기

    (converting 1D array to pandas DataFrame, and value_counts(), sort_values())

 

만약 pandas Series 내 고유한 원소별 개수를 구한 결과를 개수의 오름차순으로 정렬을 하고 싶다면 sort_values(ascending = True) 를 설정해주면 됩니다. (내림차순이 기본 설정, default to descending order)

 

import pandas as pd

## converting an array to pandas DataFrame
arr_df = pd.DataFrame(arr, columns=['x1'])
arr_df

[Out]
x1
0	a
1	c
2	c
3	b
4	a
5	b
6	b
7	c
8	a
9	c
10	b
11	a
12	a
13	a
14	c


## counting the number of unique elements in Series
arr_df['x1'].value_counts()
[Out]
a    6
c    5
b    4
Name: x1, dtype: int64


## # sorting by the counts of unique elements in ascending order
arr_df['x1'].value_counts().sort_values(ascending=True)
[Out]
b    4
c    5
a    6
Name: x1, dtype: int64

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python numpy 의 배열의 원소 값을 사전(dictionary)의 {키: 값} 쌍 ({key: value} pair) 을 이용해서, 배열의 원소 값과 사전의 키를 매핑하여 사전의 값으로 배열의 원소값을 변환하는 방법을 소개하겠습니다.

 

아래의 예에서는 다중분류 (multi-class classification) 기계학습 모델로 부터 각 관측치가 5개 classes 별 속할 확률을 배열로 반환받은 상황을 가정하여 만들어보았습니다.

 

(1) 다중분류 확률 배열로 부터 최대값의 위치 인덱스 가져오기

(2) np.vectorize() 와 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

(3) for loop 과 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

 

 

 

(1) 다중분류 확률 배열로 부터 최대값의 위치 인덱스 가져오기

 

먼저, 5개 class를 가지는 다중분류 문제에서 5개 class 별 속할 확률을 기계학습 분류 모델로 부터 아래의 'pred_proba' 라는 이름의 배열로 얻었다고 가정해보겠습니다.

 

import numpy as np

## probability for each classes
pred_proba = np.array([[0., 0., 0.2, 0.8, 0.], 
                       [0.9, 0., 0., 0., 0.1], 
                       [0., 0., 0.6, 0.2, 0.2], 
                       [0., 0., 0.5, 0.3, 0.2], 
                       [0., 0.1, 0.3, 0., 0.6], 
                       [0., 0.4, 0., 0.3, 0.3]])

pred_proba
[Out]
array([[0. , 0. , 0.2, 0.8, 0. ],
       [0.9, 0. , 0. , 0. , 0.1],
       [0. , 0. , 0.6, 0.2, 0.2],
       [0. , 0. , 0.5, 0.3, 0.2],
       [0. , 0.1, 0.3, 0. , 0.6],
       [0. , 0.4, 0. , 0.3, 0.3]])

 

 

이들 확률값 배열로 부터 하나의 예측값을 구하기 위해 이들 5개 각 class별 확률 중에서 가장 큰 값을 가지는 위치 (indices of maximum value) 의 class 를 모델이 예측한 class 라고 정의해보겠습니다.  

np.argmax(pred_proba, axis=1) 은 배열 내의 각 관측치 별 (axis = 1) 로 가장 큰 확률값의 위치의 인덱스를 반환합니다.  가령, 위의 pred_proba 의 첫번째 관측치의 5개 class 별 속할 확률은 [0., 0., 0.2, 0.8, 0.] 의 배열로서, 확률 0.8 이 가장 큰 값이므로 위치 인덱스 '3'을 반환하였습니다.

 

## positional index for maximum probability
pred_idx = np.argmax(pred_proba, axis=1)
pred_idx
[Out]
array([3, 0, 2, 2, 4, 1])

 

 

(2) np.vectorize() 와 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

 

위의 (1)번에서 구한 확률 최대값의 위치 인덱스 가지고, 이번에는 아래의 'class_map_dict'와 같이 {키: 값} 쌍 사전의 '키(key)'를 기준으로 매핑을 해서, 다중분류 모델의 예측값을 'class 이름'으로 변환을 해보겠습니다.

 

## dictionary with pairs of {index_max_proba: class_name}
class_map_dict = {
    0: 'noraml', 
    1: 'class01', 
    2: 'class02', 
    3: 'class03',
    4: 'class04'
}

class_map_dict
[Out]
{0: 'noraml', 1: 'class01', 2: 'class02', 3: 'class03', 4: 'class04'}

 

 

 

이때 dict.get(key) 를 유용하게 사용할 수 있습니다. dict.get(key) 메소드는 사전(dict)의 키에 쌍으로 대응하는 값을 반환해줍니다. 따라서 바로 위에서 정의해준 'class_map_dict'의 키 값을 넣어주면, 각 키에 해당하는 'normal'~'class04' 의 사전 값을 반환해줍니다.

 

## get() returns the value for the specified key if key is in dict.
class_map_dict.get(pred_idx[0])
[Out]
'class03'


class_map_dict.get(0)
[Out]
'noraml'

 

 

사전의 (키: 값)을 매핑하려는 배열 내 원소가 많을 경우, np.vectorize() 메소드를 이용하면 매우 편리하고 또 빠르게 사전의 (키: 값)을 매핑을 해서 배열의 값을 변환할 수 있습니다. 아래 예에서는 'class_map_dict' 의 (키: 값) 사전을 사용해서 'pred_idx'의 확률 최대값 위치 인덱스 배열을 'pred_cls' 의 예측한 클래스(레이블) 이름('normal'~'class04')으로 변환해주었습니다.

 

np.vectorize() 는 numpy의 broadcasting 규칙을 사용해서 매핑을 하므로 코드가 깔끔하고, for loop을 사용하지 않으므로 원소가 많은 배열을 처리해야 할 경우 빠릅니다.

 

## vectorization of dict.get(array_idx) for all elements of array
pred_cls = np.vectorize(class_map_dict.get)(pred_idx)

pred_cls
[Out]
array(['class03', 'noraml', 'class02', 'class02', 'class04', 'class01'],
      dtype='<U7')
      

* np.vectorize() reference: numpy.org/doc/stable/reference/generated/numpy.vectorize.html

 

 

 

(3) for loop 과 dict.get() 을 사용해서 최대값 위치 인덱스와 분류 레이블을 매핑하기

 

만약 위의 (2)번 처럼 np.vectorize() 메소드를 사용하지 않는다면, 아래처럼 for loop 사용해서 확률 최대값 위치 인덱스의 개수 만큼 순환 반복을 하면서 dict.get() 함수를 적용해주어야 합니다. 위의 (2)번 대비 코드도 길고, 또 대상 배열이 클 경우 시간도 더 오래 걸리므로 np.vectorize() 사용을 권합니다.

 

## manually using for loop
pred_cls_mat = np.empty(pred_idx.shape, dtype='object')

for i in range(len(pred_idx)):
    pred_cls_mat[i] = class_map_dict.get(pred_idx[i])
    
pred_cls_mat
[Out]
array(['class03', 'noraml', 'class02', 'class02', 'class04', 'class01'],
      dtype=object)

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

Python pandas에는 DataFrame 내 숫자형 변수의 결측값 여부를 확인(rfriend.tistory.com/260)하거나 결측값을 채우는(rfriend.tistory.com/262) 다양하고 간단한 함수를 제공합니다.

 

이번 포스팅에서는 범주형 자료의 결측값을 각 범주별 구성비율에 비례하여 확률적으로 채우는 방법을 소개하겠습니다. 가령, 'A' 범주가 50%, 'B' 범주가 30%, 'C' 범주가 20%를 구성하고 있으며, 결측값 발생 시 각 범주별 구성비율에 따라서 확률적으로 결측값을 채워넣어보겠습니다.

(엄밀히 말하면 공백 '', '   ' 나 'None' 등은 결측값이라고 말하기 곤란합니다만... 문자열 '' 을 결측값이라고 정의하겠습니다.) 

 

댓글로 위의 요건을 수행하는 Python 코드에 대한 질문을 남겨주신 분이 계셔서 그때 답변 달았던 내용을 포스팅으로 옮겨보았습니다.

 

절차는 2단계로 이루어집니다.

 

(1) 균등분포(uniform distribution)로 부터 난수를 생성하여 각 범주의 구성비율에 따라 결측값을 채우는 값을 지정하는 사용자 정의 함수 생성

(2) for loop 반복문으로 범주형 변수의 결측값일 경우 (1)번 사용자 정의 함수를 실행하여 결측값 채우기

 

 

 

먼저, 예제로 사용할 문자열로 구성된 칼럼에 결측값('')을 가진 간단한 DataFrame을 만들어보겠습니다.

 

## DataFrame with missing value in categorical variable
import pandas as pd

df = pd.DataFrame({'x1': ['A', 'A', 'C', '', 'A', 'B', 'A', 'B', 'A', 'A', 
                          'C', '', 'A', 'A', '', 'A', 'B', 'A', 'C', 'A', '']})
                          
print(df)
[Out]
   x1
0   A
1   A
2   C
3      <-- missing
4   A
5   B
6   A
7   B
8   A
9   A
10  C
11     <-- missing
12  A
13  A
14     <-- missing
15  A
16  B
17  A
18  C
19  A
20     <-- missing
20   

 

(1) 균등분포(uniform distribution)로 부터 난수를 생성하여 각 범주의 구성비율에 따라 결측값을 채우는 값을 지정하는 사용자 정의 함수 생성

 

균등분포(uniform distribution)는 구간 [min, max] 에서 값이 균등하게 퍼져있는 집단, 일어날 확률이 균등한 분포를 말합니다. 가령, 구간 [0, 1] 에서 임의로 난수를 생성할 경우 그 값이 뽑힐 확률은 모두 1로서 동일하게 됩니다. (만약 구간 [0, 10] 에서 난수를 생성할 경우 각 값이 뽑힐 확률은 모두 0.1로서 동일하게 됨).

 

numpy 의 random.uniform(min, max, size) 메소드를 사용해서 균등분포로 부터 난수를 생성할 수 있습니다. 아래는 구간 [0. 1] 에서 난수 10개를 생성한 예입니다.

 

import numpy as np

np.random.uniform(0, 1, 10)

[Out]
array([0.56947875, 0.95692566, 0.9978566 , 0.99739644, 0.00885555,
       0.92047312, 0.00443685, 0.12121749, 0.46886965, 0.32319941])

 

그럼, 이제 모집단에서 각 범주가 차지하는 비율이 'A' 범주(category, class)는 50%, 'B' 범주는 30%, 'C' 범주는 20%라고 하고, 이 각 범주별 구성비율에 비례해서 범주의 결측치를 확률적으로, 임의로 채우는 사용자 정의 함수를 정의해보겠습니다.

 

## 'A' 0.5 : 'B' 0.3 : 'C' 0.2
def cat_fill_na():
    # generate random number from uniform distrubution
    rnd_num = np.random.uniform(0, 1, 1)
    
    if rnd_num > 0.8:
        x = 'C'
    elif rnd_num > 0.5:
        x = 'B'
    else:
        x = 'A'
    
    return x

 

 

(2) for loop 반복문으로 범주형 변수의 결측값일 경우 (1)번 사용자 정의 함수를 실행하여 결측값 채우기 

 

이제 위의 (1)번에서 정의한 사용자 정의 함수를 사용해서 범주형 변수의 관측치들 중에서 결측값('')의 경우 확률적으로 범주 값을 채워넣어보겠습니다.

for i in range(df.shape[0]):
    if df['x1'].iloc[i] == '':
        df['x1'].iloc[i] = cat_fill_na
        
df
[Out]
x1
0	A
1	A
2	C
3	C  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)
4	A
5	B
6	A
7	B
8	A
9	A
10	C
11	C  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)
12	A
13	A
14	C  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)
15	A
16	B
17	A
18	C
19	A
20	B  <-- filled randomly with probability of ('A' 50%, 'B' 30%, 'C' 20%)

 

 

---------------------------------------------------------------------------------------------

참고로, 아래처럼 코드를 수행하면 단 한번만 균등분포로부터 난수를 발생하여 해당 난수값이 포함된 단 하나의 범주값을 모든 결측값에 채워넣게 되므로 이번 요건에는 적합하지 않습니다.

 

## define UDF
def cat_fill_na(x):
    if x == '':
        rnd_int = random.randint(1, 10)
        if rnd_int == 9:
            x = 'B'
        elif rnd_int == 10:
            x = 'C'
        else:
            x = 'A'
    else:
        x = x
        
    return x
    
## run UDF    
df2 = df.apply(lambda x: cat_fill_na(x['x1']), axis=1)

df2
0     A
1     A
2     C
3     A  <-- filled with the same value
4     A
5     B
6     A
7     B
8     A
9     A
10    C
11    A   <-- filled with the same value
12    A
13    A
14    A   <-- filled with the same value
15    A
16    B
17    A
18    C
19    A
20    A   <-- filled with the same value
dtype: object

 

이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이전 포스팅에서는 무작위(확률, 임의) 표본 추출과 관련하여,

- numpy.random() 메소드를 이용하여 확률분포별 확률 표본 추출, 난수 생성: https://rfriend.tistory.com/284

- 그룹별 무작위 표본 추출: https://rfriend.tistory.com/407

- 기계학습을 위한 Train, Test 데이터셋 분할: https://rfriend.tistory.com/519

- 층화 무작위 추출을 통한 Train, Test 데이터셋 분할: https://rfriend.tistory.com/520

방법에 대하여 소개하였습니다.



이번 포스팅에서는 Python pandas 모듈의 DataFrame.sample() 메소드를 사용해서 DataFrame으로 부터 무작위 (확률, 임의) 표본 추출 (random sampling) 하는 방법을 소개하겠습니다.


(1) DataFrame으로 부터 특정 개수의 표본을 무작위로 추출하기 (number)

(2) DataFrame으로 부터 특정 비율의 표본을 무작위로 추출하기 (fraction)

(3) DataFrame으로 부터 복원 무작위 표본 추출하기 (random sampling with replacement)

(4) DataFrame으로 부터 가중치를 부여하여 표본 추출하기 (weights)

(5) DataFrame으로 부터 칼럼에 대해 무작위 표본 추출하기 (axis=1, axis='column)

(6) DataFrame으로 부터 특정 칼럼에 대해 무작위 표본 추출한 결과를 numpy array로 할당하기



[ pandas DataFrame에서 무작위 (확률) 표본 추출하기: pandas.DataFrame.sample() ]



  (1) DataFrame으로 부터 특정 개수의 표본을 무작위(확률)로 추출하기 (number)


예제로 사용할 4개의 관측치와 3개의 칼럼을 가진 pandas DataFrame을 만들어보겠습니다.

(참조 [1] 의 pandas tutorial 코드 사용하였습니다.)



import pandas as pd

df = pd.DataFrame({'num_legs': [2, 4, 8, 0],
                   'num_wings': [2, 0, 0, 0],
                   'num_specimen_seen': [10, 2, 1, 8]},
                  index=['falcon', 'dog', 'spider', 'fish'])

df


num_legsnum_wingsnum_specimen_seen
falcon2210
dog402
spider801
fish008

 



DataFrame.sample() 메소드의 n 매개변수를 사용해서 특정 개수 (number)의 표본을 무작위로 추출할 수 있습니다. 그리고 random_state 매개변수는 무작위(확률) 표본 추출을 위한 난수(random number)를 생성할 때 초기값(seed number) 로서, 재현가능성(reproducibility)을 위해서 설정해줍니다.


아래 예에서는 총 4개 관측치 중에서 2개의 관측치 (n=2) 를 무작위 표본 추출해보았습니다. Index를 기준으로 n 개수 만큼 표본을 추출해서 모든 칼럼의 값을 pandas DataFrame 자료구조로 반환합니다.



df.sample(n=2, # number of items from axis to return.
          random_state=1004) # seed for random number generator for reproducibility



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (2) DataFrame으로 부터 특정 비율의 표본을 무작위로 추출하기 (fraction)


DataFrame으로 부터 특정 비율(fraction)으로 무작위 표본 추출을 하고 싶으면 frac 매개변수에 0~1 사이의 부동소수형(float) 값을 입력해주면 됩니다.



df.sample(frac=0.5, # fraction of axis items to return.
          random_state=1004)



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 



만약 비복원 추출 모드 (replace = False, 기본 설정) 에서 frac 값이 1을 초과할 경우에는 "ValueError: Replace has to be set to 'True' when upsampling the population 'frac' > 1." 이라는 에러가 발생합니다. 왜냐하면 모집단의 표본 개수 (100%, frac=1) 보다 더 많은 표본을 비복원 추출로는 할 수 없기 때문입니다. (복원 추출의 경우 동일한 관측치를 다시 표본 추출할 수 있으므로 frac > 1 인 경우도 가능함.)



## ValueError: Replace has to be set to `True` when upsampling the population `frac` > 1.
df.sample(frac=1.5,
          random_state=1004)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-45-2fcc4494d7ae> in <module>
----> 1 df.sample(frac=1.5, # fraction of axis items to return. 
      2           random_state=1004)

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5326             n = 1
   5327         elif frac is not None and frac > 1 and not replace:
-> 5328             raise ValueError(
   5329                 "Replace has to be set to `True` when "
   5330                 "upsampling the population `frac` > 1."

ValueError: Replace has to be set to `True` when upsampling the population `frac` > 1.

 



만약 DataFrame.sample() 메소드에서 표본 개수 n 과 표본추출 비율 frac 을 동시에 설정하게 되면 "ValueError: Please enter a value for 'frac' OR 'n', not both" 에러가 발생합니다. n 과 frac 둘 중에 하나만 입력해야 합니다.



## parameter 'n' and 'frac' cannot be used at the same time.
## ValueError: Please enter a value for `frac` OR `n`, not both
df.sample(n=2, frac=0.5)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-b31ebc150882> in <module>
      1 ## parameter 'n' and 'frac' cannot be used at the same time.
      2 ## ValueError: Please enter a value for `frac` OR `n`, not both
----> 3 df.sample(n=2, frac=0.5)

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5335             n = int(round(frac * axis_length))
   5336         elif n is not None and frac is not None:
-> 5337             raise ValueError("Please enter a value for `frac` OR `n`, not both")
   5338 
   5339         # Check for negative sizes

ValueError: Please enter a value for `frac` OR `n`, not both

 




  (3) DataFrame으로 부터 복원 무작위 표본 추출하기

      (random sampling with replacement)


한번 추출한 표본을 다시 모집단에 되돌려 넣고 추출하는 방법을 복원 추출법 (sampling with replacement) 이라고 합니다. 복원 추출법을 사용하면 동일한 표본이 중복해서 나올 수 있습니다.


DataFrame.sample() 메소드에서는 repalce=True 로 설정하면 복원 추출을 할 수 있습니다. 많은 경우 한번 추출된 표본은 되돌려 놓지 않고 표본을 추출하는 비복원 추출(sampling without replacement)을 사용하며, 기본 설정은 replace=False 입니다.



## replace=True: random sampling with replacement
df.sample(n=8, # or equivalently: frac=2
          replace=True, # random sampling with replacement
          random_state=1004)



num_legsnum_wingsnum_specimen_seen
spider801
fish008
fish008
dog402
fish008
fish008
fish008
spider801

 



만약 비복원 추출 모드 (replace=False) 에서 원본 DataFrame 의 관측치 개수 (행의 개수) 보다 많은 수의 표본을 무작위 추출하고자 한다면 "ValueError: Cannot take a larger sample than population when 'replace=False'" 에러 메시지가 발생합니다.  모집단이 가지고 있는 관측치 수보다 더 많은 수의 표본을 중복이 없는 "비복원 추출"로는 불가능하기 때문입니다.

(복원추출(sampling with replacement, replace=True) 모드 에서는 동일한 표본을 중복 추출이 가능하므로 모집단 관측치 수보다 많은 수의 표본 추출이 가능함.)



## ValueError: Cannot take a larger sample than population when 'replace=False'
df.sample(n=8,
          replace=False # random sampling without replacement
)


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-42-40c76bd4c271> in <module>
      1 ## replace=True: random sampling with replacement
----> 2 df.sample(n=8, # or equivalently: frac=2
      3           replace=False # random sampling without replacement
      4 )

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/generic.py in sample(self, n, frac, replace, weights, random_state, axis)
   5343             )
   5344 
-> 5345         locs = rs.choice(axis_length, size=n, replace=replace, p=weights)
   5346         return self.take(locs, axis=axis)
   5347 

mtrand.pyx in numpy.random.mtrand.RandomState.choice()

ValueError: Cannot take a larger sample than population when 'replace=False'

 




  (4) DataFrame으로 부터 가중치를 부여하여 표본 추출하기 (weights)


만약에 DataFrame 내의 특정 칼럼의 값을 기준으로 가중치를 부여하여 무작위 표본 추출을 하고 싶다면 DataFrame.sample() 메소드의 weights 매개변수에 가중치로 사용할 칼럼 이름을 설정해주면 됩니다.


아래 예에서는 df DataFrame의 'num_specimen_seen' 칼럼의 값이 크면 클수록 표본으로 뽑힐 확률이 더 크도록 가중치(weights)를 부여해보았습니다. 아니나 다를까, 'num_specimen_seen' 값이 10, 8 인 falcon, fish가 표본으로 추출이 되었네요. 

(물론, 표본추출 시행을 계속 하다보면 num_specimen_seen 값이 1인 spider나 2인 dog 도 표본으로 뽑히는 때가 오긴 올겁니다. 다만, num_specimen_seen 값의 가중치로 인해 표본 추출될 확률이 낮아 상대적으로 작은 빈도로 추출이 되겠지요.)



## Using a DataFrame column as weights.
## Rows with larger value in the num_specimen_seen column are more likely to be sampled.
df.sample(n=2,
          weights='num_specimen_seen'

)



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (5) DataFrame으로 부터 칼럼에 대해 무작위 표본 추출하기 (axis=1, axis='column)


위의 (1) ~ (4) 까지는 axis=0, 즉 Index 에 대해서 무작위 표본 추출을 해서 전체 칼럼의 값을 반환하였습니다.


DataFrame.sample() 메소드의 axis 매개변수를 axis=1, 또는 axis='column' 으로 설정을 해주면 여러개의 칼럼에 대해서 무작위로 표본 추출을 해서 전체 행(all rows, random sampled columns) 을 반환합니다. (이런 요건의 분석은 그리 많지는 않을것 같습니다만, 이런 기능도 있다는 정도로만 알아두면 되겠습니다.)



## Axis to sample: by column
df.sample(n=2,
          random_state=1004,
          axis=1) # or equivalently, axis='column'



num_legsnum_wings
falcon22
dog40
spider80
fish00

 



axis 매개변수의 기본 설정은 대부분의 분석 요건에 해당하는 Index 기준의 무작위 표본 추출인 axis=0 (or, axis='index') 입니다.



## Axis to sample: by index
df.sample(n=2,
          random_state=1004,
          axis=0) # or equivalently, axis='index', default



num_legsnum_wingsnum_specimen_seen
falcon2210
fish008

 




  (6) DataFrame으로 부터 특정 칼럼에 대해 무작위 표본 추출한 결과를

       numpy array로 할당하기


만약 DataFrame의 여러개의 칼럼 중에서 특정 하나의 칼럼에 대해서만 무작위 표본 추출을 하고 싶다면 DataFrame['column_name'] 형식으로 먼저 Series 로 특정 칼럼의 값을 가져오고, 이에 대해서 sample() 메소드를 사용하면 됩니다.



## Sampling only for a column
df['num_legs'].sample(n=2, random_state=1004)


[Out] 
falcon 2 fish 0 Name: num_legs, dtype: int64

 



df['num_specimen_seen'].sample(n=2, random_state=1004)


[Out]
falcon 10 fish 8 Name: num_specimen_seen, dtype: int64

 



이렇게 DataFrame으로 부터 특정 하나의 칼럼 값을 Series 로 인덱싱해와서 무작위 표본 추출을 하면, 역시 그 결과 객체의 데이터 유형도 Series 입니다.



## Assigning sampling results as Series
samp_Series = df['num_legs'].sample(n=2)
type(samp_Series)


[Out] pandas.core.series.Series

 



만약, DataFrame으로 부터 특정 하나의 칼럼 값 Series 로 부터의 무작위 표본 추출 결과를 Numpy Array로 할당해서 결과를 가져오고 싶다면 numpy.array() 로 Series 를 array 로 변환해주면 됩니다.



## Assigning sampling results as numpy array
import numpy as np
samp_array = np.array(df['num_legs'].sample(n=2))
type(samp_array)

[Out] numpy.ndarray


samp_array

[Out] array([0, 2])




[ Reference ]

* pandas.DataFrame.sample: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.sample.html



이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요!  :-)




728x90
반응형
Posted by Rfriend
,