이번 포스팅에서는 Python의 pandas DataFrame 에서 여러개 칼럼의 조건을 일부(any)만 또는 전부(all) 만족하는 행 가져오기하는 방법을 소개하겠습니다. 

 

pandas DataFrame의 any(), all() 메소드를 이용하면 매우 간단하게 조건을 만족하는 행을 색인해서 가져올 수 있습니다. 

 

(1) pandas DataFrame 의 칼럼 중에서 1개 이상의 칼럼이 True 인 행 가져오기: pd.DataFrame.any(axis=1)

(2) pandas DataFrame 의 칼럼 중에서 모든 칼럼이 True 인 행 가져오기: pd.DataFrame.all(axis=1)

 

 

pandas DataFrae any(axis=1), all(axis=1)

 

 

 

먼저, 예제로 사용할 간단한 pandas DataFrame 을 만들어 보겠습니다. 4개의 칼럼을 가지고 있고, 결측값도 하나 넣어습니다. 

 

import pandas as pd
import numpy as np

## creating a sample DataFrame with 4 columns
df = pd.DataFrame({
    'x1': [0, 1, 2, 3, 4], 
    'x2': [-2, -1, 0, 1, 3], 
    'x3': [-4, -3, -2, -1, -4], 
    'x4': [np.nan, 0, 2, 3, -10]
})

print(df)
#    x1  x2  x3    x4
# 0   0  -2  -4   NaN
# 1   1  -1  -3   0.0
# 2   2   0  -2   2.0
# 3   3   1  -1   3.0
# 4   4   3  -4 -10.0

 

 

 

(1) pandas DataFrame 의 칼럼 중에서 1개 이상의 칼럼이 True 인 행 가져오기: pd.DataFrame.any(axis=1)

 

아래처럼 np.abs(df) > 2 를 하면 모든 칼럼의 모든 행에 대해서 절대값(absolute value)이 2보다 큰지 아닌지 여부에 대해 True/False 블리언 값을 반환합니다. 

 

## returns boolean for all columns and rows
np.abs(df) > 2

# 	x1	   x2	   x3	   x4
# 0	False	False	True	False
# 1	False	False	True	False
# 2	False	False	False	False
# 3	True	False	False	True
# 4	True	True	True	True

 

 

이제 칼럼 4개 중에서 절대값(absolute value)이 2보다 큰 값이 단 하나라도 존재하는 행을 가져와 보겠습니다. 이때 '칼럼들에 대해 단 하나라도 존재하면'이라는 조건 판단은 pandas.DataFrame.any(axis=1) 메소드를 사용하면 편리합니다. 

 

any(axis =1) 에서 axis=1 을 설정해주면 칼럼 축을 기준으로 조건 만족여부를 평가합니다. 기본설정값이 axis=0 이므로 반드시 명시적으로 any(axis=1) 처럼 축을 지정해주어야 합니다. 

 

결측값이 있어도 다른 칼럼에서 조건을 만족하면 해당 행을 가져옵니다. 

 

## (1) DataFrame.any(axis=0, bool_only=None, skipna=True, level=None, **kwargs)
## pd.DataFrame.any(): Return whether any element is True, potentially over an axis.

df[(np.abs(df) > 2).any(axis=1)]
#    x1	x2	x3	x4
# 0	 0	-2	-4	NaN
# 1	 1	-1	-3	0.0
# 3	 3	1	-1	3.0
# 4	 4	3	-4	-10.0

 

 

 

pandas.DataFrame.any(axis=1) 메소드를 사용하지 않고, 아래처럼 블리언 값(True=1, False=0)을 칼럼 축으로 더해서(sum(axis=1)), 그 더한 값이 0보다 큰 행을 인덱싱해서 가져오는 방법을 써도 되긴 합니다. 

 

## or equivalantly
df[(np.abs(df) > 2).sum(axis=1) > 0]

#    x1	x2	x3	x4
# 0	 0	-2	-4	NaN
# 1	 1	-1	-3	0.0
# 3	 3	1	-1	3.0
# 4	 4	3	-4	-10.0

 

 

 

 

(2) pandas DataFrame 의 칼럼 중에서 모든 칼럼이 True 인 행 가져오기: pd.DataFrame.all(axis=1)

 

이번에는 pandas.DataFrame.all(axis=1)을 이용해서 DataFrame에 있는 4개의 모든 칼럼이 조건을 만족하는 행만 가져오기를 해보겠습니다. 

 

## DataFrame.all(axis=0, bool_only=None, skipna=True, level=None, **kwargs)
## Return whether all elements are True, potentially over an axis.

df[(np.abs(df) > 2).all(axis=1)]
#    x1	  x2	 x3	  x4
# 4	 4	3	-4	-10.0

 

 

 

아래는 pandas.DataFrame.all() 메소드를 사용하지 않고, 대신에 조건 만족여부에 대한 블리언 값을 칼럼 축으로 전부 더한 후, 이 블리언 합이 칼럼 개수와 동일한 행을 가져와본 것입니다. 위의 pandas.DataFrame.all(axis=1) 보다 코드가 좀더 길고 복잡합니다. 

 

## or, equivalently
df[(np.abs(df) > 2).sum(axis=1) == df.shape[1]]

#     x1	 x2	 x3	 x4
# 4	 4	3	-4	-10.0

 

[ Reference ]

* pandas.DataFrame.any() : https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.any.html  
* pandas.DataFrame.all(): https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.all.html

 

 

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

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

 

728x90
반응형
Posted by R Friend Rfriend

댓글을 달아 주세요

프로그래밍 코드를 짜다보면 수행 절차나 방법, 사용하는 메소드에 따라서 수행 시간이 차이가 나는 경우가 종종 있습니다. 그리고 성능이 중요해서 여러가지 방법을 테스트해보면서 가장 실행시간이 짧도록 튜닝하면서 최적화하기도 합니다. 

 

이번 포스팅에서는 Python에서 코드를 실행시켰을 때 소요된 시간을 측정하는 2가지 방법을 소개하겠습니다. 

 

(1) datetime.now() 메소드 이용해서 실행 시간 측정하기

(2) %timeit 로 실행 시간 측정하기

 

 

python : measuring the execution time of code snippets

 

먼저, 예제로 사용할 샘플 데이터셋으로서, 1억개의 값을 가지는 xarr, yarr 의 두개의 배열(array)를 만들어 보겠습니다. 그리고 배열 내 각 1억개의 값 별로 True/False 의 조건값을 가지는 cond 라는 배열도 난수를 생성시켜서 만들어보겠습니다. 

 

import numpy as np

## generating sample array with 100 million values
xarr = np.arange(100000000)
yarr = np.zeros(100000000)
cond = np.where(np.random.randn(100000000)>0, True, False)


cond[:10]
# [Out] array([False,  True,  True, False, False,  True,  True,  True,  
#              True, True])

 

 

위에서 만든 1억개의 원소를 가지는 배열을 가지고 조건값으로 True/False 블리언 값 여부에 따라서 True 조건값 이면 xarr 배열 내 값을 가지고, False 조건값이면 yarr 배열 내 값을 가지는 새로운 배열을 만들어보겠습니다. 이때 (1) List Comprehension 방법과, (2) NumPy의 Vectorized Operations 방법 간 수행 시간을 측정해서 어떤 방법이 더 빠른지 성능을 비교해보겠습니다.

(물론, Vectorized Operations이 for loop 순환문을 사용하는 List Comprehension보다 훨~씬 빠릅니다! 눈으로 직접 확인해 보겠습니다. )

 

## Let's compare the elapsed time between 2 methods 
## (list comprehension vs. vectorized operations)

## (1) List Comprehension
new_arr = [(x if c else y) for (x, y, c) in zip(xarr, yarr, cond)]

## (2) Vectorized Operations in NumPy 
new_arr = np.where(cond, xarr, yarr)

 

 

 

(1) datetime.now() 메소드 이용해서 실행 시간 측정하기

 

datetime 모듈은 날짜, 시간, 시간대(time zone) 등을 다루는데 사용하는 모듈입니다 datetime.now() 메소드는 현재의 로컬 날짜와 시간을 반환합니다. 실행 시간을 측정할 코드 바로 앞에 start_time = datetime.now() 로 시작 날짜/시간을 측정해놓고, 실행할 코드가 끝난 다음 줄에 time_elapsed = datetime.now() - start_time 으로 '끝난 날짜/시간'에서 '시작 날짜/시간'을 빼주면 '코드 실행 시간'을 계산할 수 있습니다. 

 

아래 결과를 비교해보면 알 수 있는 것처럼, for loop 순환문을 사용하는 List Comprehension 방법보다 NumPy의 Vectorized Operation이 약 38배 빠른 것으로 나오네요. 

 

## (1) -- measuring the elapsed time using datetime

## (a) List Comprehension
from datetime import datetime
start_time = datetime.now() 
list_comp_for_loop = [(x if c else y) for (x, y, c) in zip(xarr, yarr, cond)]
time_elapsed = datetime.now() - start_time 

print('Time elapsed (hh:mm:ss.ms) {}'.format(time_elapsed))
# Time elapsed (hh:mm:ss.ms) 0:00:17.753036

np.array(list_comp_for_loop)[:10]
# array([0., 1., 2., 0., 0., 5., 6., 7., 8., 9.])



## (b) Vectorized Operations in NumPy 
start_time = datetime.now() 
np_where_vectorization = np.where(cond, xarr, yarr)
time_elapsed = datetime.now() - start_time 

print('Time elapsed (hh:mm:ss.ms) {}'.format(time_elapsed))
# Time elapsed (hh:mm:ss.ms) 0:00:00.462215

np_where_vectorization[:10]
# array([0., 1., 2., 0., 0., 5., 6., 7., 8., 9.])

 

 

(2) %timeit 로 실행 시간 측정하기

 

다음으로 Python timeit 모듈을 사용해서 짧은 코드의 실행 시간을 측정해보겠습니다. timeit 모듈은 터미널의 command line 과 Python IDE 에서 호출 가능한 형태의 코드 둘 다 사용이 가능합니다. 

 

아래에는 Jupyter Notebook에서 %timeit [small code snippets] 로 코드 수행 시간을 측정해본 예인데요, 여러번 수행을 해서 평균 수행 시간과 표준편차를 보여주는 특징이 있습니다. 

 

## (2) measuring the elapsed time using timeit

## (a) List Comprehension
import timeit

%timeit list_comp_for_loop = [(x if c else y) for (x, y, c) in zip(xarr, yarr, cond)]
# 17.1 s ± 238 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


## (b) Vectorized Operations in NumPy 
%timeit np_where_vectorization = np.where(cond, xarr, yarr)
# 468 ms ± 8.75 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

 

 

[Reference]

* Python datetime: https://docs.python.org/3/library/datetime.html

* Python timeit: "measuring the execution time of small code snippets"
   : https://docs.python.org/3/library/timeit.html

 

 

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

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

 

728x90
반응형
Posted by R Friend Rfriend

댓글을 달아 주세요

Word counts 할 때 많이 사용하는 코드인데요, 이번 포스팅에서는 

  (1) 리스트에서 원소별 개수를 세서 {Key:Value} 쌍의 Dictionary를 만들고 

  (2) 원소별 개수를 세어놓은 Dictionary에서 개수 상위 n 개의 {Key:Value} 쌍을 가져오기

하는 방법을 소개하겠습니다. 

 

 

counts dictionary, getting top n

 

(1) 리스트에서 원소별 개수를 세서 {Key:Value} 쌍의 Dictionary를 만들기

 

먼저, 예제로 사용할 간단한 리스트를 만들어보겠습니다. 

 

## creating sample lists
my_list = ['a', 'f', 'a', 'b', 'a', 'a', 'c', 'b', 
           'c', 'e', 'a', 'c', 'b', 'f', 'c']
           
print(my_list)
# ['a', 'f', 'a', 'b', 'a', 'a', 'c', 'b', 'c', 'e', 'a', 'c', 'b', 'f', 'c']

 

 

다음으로, 원소별 개수를 세서 저장할 비어있는 Dictionary 인 counts={} 를 만들어놓고, for loop 순환문으로 리스트의 원소를 하나씩 순서대로 가져다가 Dictionary counts 의 Key 값에 해당 원소가 들어있으면 +1을 하고, Key 값에 해당 원소가 안들어 있으면 해당 원소를 Key 값으로 등록하고 1 을 값으로 입력해 줍니다.  

 

def get_counts(seq): 
    counts = {}
    for x in seq:
        if x in counts:
            counts[x] += 1
        else:
            counts[x] = 1
    return counts
    
 
counts = get_counts(my_list)


print(counts)
# {'a': 5, 'f': 2, 'b': 3, 'c': 4, 'e': 1}


## access value by key
counts['a']
# 5

 

 

 

(2) 원소별 개수를 세어놓은 Dictionary에서 개수 상위 n 개의 {Key:Value} 쌍을 가져오기

 

Dictionary를 정렬하는 방법에 따라서 두 가지 방법이 있습니다.

 

(a) sorted() 메소드를 이용해서 key=lambda x: x[1] 로 해서 정렬 기준을 Dictionary의 Value 로 하여 내림차순으로 정렬(reverse=True) 하고, 상위 n 개까지만 슬라이싱해서 가져오는 방법입니다. 

 

## way 1
## reference: https://rfriend.tistory.com/473
def top_n(count_dict, n=3):
    return sorted(count_dict.items(), reverse=True, key=lambda x: x[1])[:n]
    

## getting top 2
top_n(counts, n=2)
# [('a', 5), ('c', 4)]

 

 

 

(b) 아래는 dict.items() 로 (Key, Value) 쌍을 for loop 문을 돌리면서 (Value, Key) 로 순서를 바꿔서 리스트 [] 로 만들고 (list comprehension), 이 리스트에 대해서 sort(reverse=True) 로 Value 를 기준으로 내림차순 정렬한 후에, 상위 n 개까지만 슬라이싱해서 가져오는 방법입니다. 

 

## way2
## reference: https://rfriend.tistory.com/281
def top_n2(count_dict, n=3):
    val_key = [(v, k) for k, v in count_dict.items()]
    val_key.sort(reverse=True)
    return val_key[:n]
    
## getting top 2
top_n2(counts, n=2)
# [(5, 'a'), (4, 'c')]

 

 

[Reference]

* Dictionary 정렬: https://rfriend.tistory.com/473

* List 정렬: https://rfriend.tistory.com/281

 

 

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

행복한 데이터 과학자 되세요. 

 

728x90
반응형
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 Python pandas 의 DataFrame에서 

 

(1) 그룹별로 x 칼럼을 정렬 후 누적 비율을 구한 후 (calculating the cumulative proportion by groups)  

(2) 그룹별로 특정 분위수의 위치 구하기 (getting the indices for a specific quantile p by groups)

 

하는 방법을 소개하겠습니다. 

 

그룹별로 연산을 수행하므로 pandas.DataFrame.groupby().apply(UDF) 형식으로 구문을 작성할 거예요. 

 

 

[ pandas DataFrame에서 그룹별로 정렬 후 누적 비율을 구한 후에 --> 그룹별로 특정 분위수 위치 구하기 ]

pandas getting cumulative proportion and quantile

 

 

 

먼저, 예제로 사용하기 위해 그룹('grp') 칼럼별 값('x')을 가지는 간단한 pandas DataFrame을 만들어보겠습니다. 

 

import numpy as np
import pandas as pd

df = pd.DataFrame({
    'grp': ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'], 
    'x': [3, 1, 2, 7, 4, 4, 2, 5, 9, 7]})
    
print(df)
#   grp  x
# 0   a  3
# 1   a  1
# 2   a  2
# 3   a  7
# 4   a  4
# 5   b  4
# 6   b  2
# 7   b  5
# 8   b  9
# 9   b  7

 

 

 

(1) 그룹별로 x 칼럼을 정렬 후 누적 비율을 구한 후 (calculating the cumulative proportion by groups)  

 

그룹별로 x 칼럼에 대한 누적 비율을 구하기 위해, 먼저 그룹별로 x 칼럼의 비율(proportion)을 계산해서 'prop' 라는 칼럼을 추가해보겠습니다. x_prop() 라는 사용자 정의 함수를 정의한 후, df.groupby('grp').apply(x_prop) 처럼 그룹에 apply() 메소드로 사용자 정의 함수를 적용해서 연산을 했습니다. 

 

## adding the proportion column by group
def x_prop(group):
    group['prop'] = group.x / group.x.sum()
    return group

df = df.groupby('grp').apply(x_prop)


print(df)
#   grp  x      prop
# 0   a  3  0.176471
# 1   a  1  0.058824
# 2   a  2  0.117647
# 3   a  7  0.411765
# 4   a  4  0.235294
# 5   b  4  0.148148
# 6   b  2  0.074074
# 7   b  5  0.185185
# 8   b  9  0.333333
# 9   b  7  0.259259


## checking the sanity
df.groupby('grp').prop.sum()
#      grp
# a    1.0
# b    1.0
# Name: prop, dtype: float64

 

 

앞에서 계산한 그룹별 x 칼럼의 비율 'prop'을 그룹별로 내림차순(descending order)으로 정렬해서 보면 아래와 같습니다. 

 

## sorting in descending order by prop 
df.sort_values(by=['grp', 'prop'], ascending=False)

#     grp	x	prop
# 8	b	9	0.333333
# 9	b	7	0.259259
# 7	b	5	0.185185
# 5	b	4	0.148148
# 6	b	2	0.074074
# 3	a	7	0.411765
# 4	a	4	0.235294
# 0	a	3	0.176471
# 2	a	2	0.117647
# 1	a	1	0.058824

 

 

pandas 의 cumsum() 메소드를 사용해서 그룹별 x칼럼의 비율 'prop'의 누적 합계 (cumulative sum) 인 'cum_prop' 를 그룹별로 계산해보겠습니다. 역시 비율 'prop'에 대해서 누적 합계(cum_sum)를 구하는 사용자 정의 함수 'cum_prop()'를 먼저 정의한 후에, 이를 df.groupby('grp').apply(cum_prop) 처럼 apply() 메소드에 사용자 정의함수를 적용해서 계산했습니다. 

 

## sorting in descending order by prop and calculating the cumulative sum of prop
def cum_prop(group):
    group['cum_prop'] = group.sort_values(
        by='prop', ascending=False).prop.cumsum()
    return group

df = df.groupby('grp').apply(cum_prop)


df.sort_values(by=['grp', 'cum_prop'])

#     grp	x	prop	        cum_prop
# 3	a	7	0.411765	0.411765
# 4	a	4	0.235294	0.647059
# 0	a	3	0.176471	0.823529
# 2	a	2	0.117647	0.941176
# 1	a	1	0.058824	1.000000
# 8	b	9	0.333333	0.333333
# 9	b	7	0.259259	0.592593
# 7	b	5	0.185185	0.777778
# 5	b	4	0.148148	0.925926
# 6	b	2	0.074074	1.000000

 

 

 

위의 예시는 간단한 편이므로 아래처럼 사용자 정의 함수를 정의하는 대신에 apply() 메소드 안에 바로 lambda 로 'prop'에 대해서 내림차순 정렬 후 누적 합계를 구하는 함수를 바로 써줘도 됩니다. 

 

## or, equivalentsly, using lambda function for cumulative proportion
df.groupby('grp').apply(lambda x: x.sort_values(by='prop', ascending=False).prop.cumsum())

# grp   
# a    3    0.411765
#      4    0.647059
#      0    0.823529
#      2    0.941176
#      1    1.000000
# b    8    0.333333
#      9    0.592593
#      7    0.777778
#      5    0.925926
#      6    1.000000
# Name: prop, dtype: float64

 

 

 

(2) 그룹별로 특정 분위수의 위치 구하기 (getting the indices for a specific quantile p by groups)

 

이제 위에서 구한 '그룹별 비율의 누적 합계('cum_prop')'에 대해서 pandas.Series.searchsorted(values, side='left') 메소드를 사용해서 특정 비율이 들어갈 위치를 구해보겠습니다.

 

비율에 대해 내림차순 정렬 후 누적 합계를 구한 값에 대해 특정 값이 들어갈 위치를 구하는 것이므로, 결과적으로 자료 크기 순서에 따른 위치값인 분위수(quantile) 를 구할 수 있게 됩니다. 인덱스가 '0'부터 시작하므로 위치를 구하기 위해서 반환받는 값에 '+1' 을 해주었습니다. 

 

그룹별로 특정 분위수의 위치를 구하고 싶으므로, 분위수를 구하는 사용자 정의 함수인 'quantile_idx()' 함수를 정의한 후에, 이를 df.groupby('grp').apply(quantile_idx, p) 처럼 apply() 메소드에 사용자 정의 함수와 매개변수 p를 입력해서 적용해주면 되겠습니다.

 

그룹별로 분위수 p=0.2, p=0.5, p=0.7 인 곳의 위치를 구해보니 잘 작동하는군요. 

 

## pandas.Series.searchsorted
## Series.searchsorted(value, side='left', sorter=None)[source]
## Find indices where elements should be inserted to maintain order.

def quantile_idx(group, p=0.5):
    group = group.sort_values(by='cum_prop', ascending=True)
    return group.cum_prop.searchsorted(p) + 1
    

## getting the index of quantile p=0.2 by group
df.groupby('grp').apply(quantile_idx, p=0.2)

# grp
# a    1
# b    1
# dtype: int64


## getting the index of quantile p=0.5 by group
df.groupby('grp').apply(quantile_idx, p=0.5)

# grp
# a    2
# b    2
# dtype: int64


## getting the index of quantile p=0.7 by group
df.groupby('grp').apply(quantile_idx, p=0.7)

# grp
# a    3
# b    3
# dtype: int64

 

 

[Reference]

* pandas.Series.searchsorted() method:  https://pandas.pydata.org/docs/reference/api/pandas.Series.searchsorted.html

 

 

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

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

 

728x90
반응형
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 

 (1) 그룹별로 x 칼럼을 기준으로 내림차순 정렬 후 (sorting by x in ascending order)

 (2) 그룹별로 y 칼럼의 첫번째 값, 마지막 값을 DataFrame에 칼럼 추가하기

하는 2가지 방법을 소개하겠습니다. 

 

(방법 1) pandas.DataFrame 의 transform('first', 'last') 메소드를 사용하는 방법

(방법 2) 그룹별 y의 첫번째 값, 마지막 값을 구해 DataFrame을 만들고, merge() 메소드로 합치는 방법

 

pandas DataFrame sort_values() groupby() transform('first') transform('last')

 

 

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

 

import numpy as np
import pandas as pd

df = pd.DataFrame({
    'grp': ['a', 'a', 'a', 'b', 'b', 'b'], 
    'x': [2, 3, 1, 4, 6, 5], 
    'y': [10, 20, 30, 40, 50, 60]
}) 

df

#   grp	x	y
# 0	a	2	10
# 1	a	3	20
# 2	a	1	30
# 3	b	4	40
# 4	b	6	50
# 5	b	5	60

 

 

 

(방법 1) pandas.DataFrame 의 transform('first', 'last') 메소드를 사용하는 방법

 

그룹별로 'x' 칼럼을 기준으로 내림차순으로 정렬하려면 df.sort_values(by=['grp', 'x']) 메소드를 사용합니다. 

 

## sorting by 'grp' and 'x' in ascnding order
df.sort_values(by=['grp', 'x'])

#   grp	x	y
# 2	a	1	30
# 0	a	2	10
# 1	a	3	20
# 3	b	4	40
# 5	b	5	60
# 4	b	6	50

 

 

그러면, 이제 x를 기준으로 내림차순 정렬한 후에 'grp' 그룹별로 y 칼럼의 첫번째 값('first')과 마지막 값('last')을 가져다가 기존의 df DataFrame에 새로운 칼럼을 추가해 보겠습니다. groupby('grp') 메소드로 'grp' 그룹별 연산을 하게 되고, transform('first')는 첫번째 값을 가져다가 DataFrame에 칼럼을 추가하며, transform('last')는 마지막 값을 가져다가 DataFrame에 칼럼을 추가합니다. 

 

## adding columns of the first and last value of y by group
df['y_first'] = df.sort_values(by=['grp', 'x'])\
    .groupby('grp').y.transform('first')
df['y_last'] = df.sort_values(by=['grp', 'x'])\
    .groupby('grp').y.transform('last')


 df
# 	grp	x	y	y_first	y_last
# 0	a	2	10	30	20
# 1	a	3	20	30	20
# 2	a	1	30	30	20
# 3	b	4	40	40	50
# 4	b	6	50	40	50
# 5	b	5	60	40	50

 

 

 

(방법 2) 그룹별 y의 첫번째 값, 마지막 값을 구해 DataFrame을 만들고, merge() 메소드로 합치는 방법

 

두번째 방법은 그룹별로 x를 기준으로 정렬 후 그룹별로 y 값의 첫번째 값과 마지막 값을 구해서 별도의 DataFrame을 만든 후에, 이를 원래의 DataFrame에 merge() 하는 것입니다. DB의 테이블을 join 하는 것과 유사한 방식이예요. 

 

## creating a sample DataFrame with 2 groups
df = pd.DataFrame({
    'grp': ['a', 'a', 'a', 'b', 'b', 'b'], 
    'x': [2, 3, 1, 4, 6, 5], 
    'y': [10, 20, 30, 40, 50, 60]
}) 


## making a DataFrame with the first and last values of y by groups
y_first = df.sort_values(by='x').groupby('grp').y.first()
y_last = df.sort_values(by='x').groupby('grp').y.last()

df_grp_fst_lst = pd.DataFrame({
    'y_first': y_first, 
    'y_last': y_last
})

df_grp_fst_lst
#     y_first	y_last
# grp
# a	      30	  20
# b	      40	  50

 

 

 

pd.merge(DataFrame1, DataFrame2, how='left', on='key') 방식으로 key를 기준으로 Left Join 하면 되겠네요. 

 

## merging df_grp_fst_lst to df DataFrame by left join on 'grp'
df2 = pd.merge(df, df_grp_fst_lst, how='left', on='grp') 
# or, equivalently: df2= df.merge(df_grp_fst_lst, how='left', on='grp')


df2
# 	grp	x	y	y_first	y_last
# 0	a	2	10	30	20
# 1	a	3	20	30	20
# 2	a	1	30	30	20
# 3	b	4	40	40	50
# 4	b	6	50	40	50
# 5	b	5	60	40	50

 

 

* pandas DataFrame merge(): https://rfriend.tistory.com/258

* pandas DataFrame transform(): https://rfriend.tistory.com/403

 

 

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

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

 

728x90
반응형
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 시계열 데이터에서

  (1) TimeStamp 행별로 칼럼별 비율을 구하고 

  (2) 시도표 (time series plot) 를 그리기

하는 방법을 소개하겠습니다. 

 

먼저, 예제로 사용할 간단한 pandas DataFrame을 만들어보겠습니다. index 로 2000년 ~ 2021년까지의 년도를 사용하고, 성별로 'M', 'F'의 두 개의 칼럼에 포아송분포로 부터 난수를 발생시켜서 만든 도수(frequency)를 가지는 DataFrame 입니다. 

 

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


## creating a sample DataFrame
ts = np.arange(2000, 2022) # from year 2000 to 2021

print(ts)
# [2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013
#  2014 2015 2016 2017 2018 2019 2020 2021]

np.random.seed(1) # for reproducibility
M = np.arange(len(ts)) + np.random.poisson(lam=10, size=len(ts))
F = np.arange(len(ts))[::-1] + np.random.poisson(lam=2, size=len(ts))

df = pd.DataFrame({'M': M, 'F': F}, index=ts)

df.head()
#       M	F
# 2000	9	21
# 2001	7	24
# 2002	9	20
# 2003	12	19
# 2004	13	18

 

 

(1) TimeStamp 행별로 칼럼별 비율을 구하기

 

먼저, pandas DataFrame 에서 합(sum)을 구할 때 각 TimeStamp 별로 칼럼 축(axis = 1) 으로 합을 구해보겠습니다. 

 

## calculating summation by rows
df.sum(axis=1).head()

# 2000    30
# 2001    31
# 2002    29
# 2003    31
# 2004    31
# dtype: int64

 

 

참고로, index 축으로 칼럼별 합을 구할 때는 df.sum(axis=0) 을 해주면 됩니다. sum(axis=0) 이 기본설정값이므로 df.sum() 하면 동일한 결과가 나옵니다. 

 

## summation by index axis
df.sum(axis=0) # default setting

# M    426
# F    274
# dtype: int64

 

 

pandas DataFrame에서 div() 메소드를 사용하면 각 원소를 특정 값으로 나눌 수 있습니다. 가령, 위의 예제 df DataFrame의 각 원소를 10으로 나눈다고 하면 아래처럼 df.div(10) 이라고 해주면 됩니다. (나누어주는 값 '10' 이 broadcasting 되어서 각 원소를 나누어주었음.)

 

## pd.DataFrame.div()
## : Get Floating division of dataframe and other, 
##   element-wise (binary operator truediv).
df.div(10).head()

#         M	F
# 2000	0.9	2.1
# 2001	0.7	2.4
# 2002	0.9	2.0
# 2003	1.2	1.9
# 2004	1.3	1.8

 

 

이제 df DataFrame의 각 원소를 각 원소가 속한 TimeStamp별로 칼럼 축(axis=1)으로 합한 값(df.sum(axis=1))으로 나누어주면 우리가 구하고자 하는 각 TimeStamp별 칼럼별 비율을 구할 수 있습니다. 

 

df.div(df.sum(axis=1), axis=0).head()

#         M	        F
# 2000	0.300000	0.700000
# 2001	0.225806	0.774194
# 2002	0.310345	0.689655
# 2003	0.387097	0.612903
# 2004	0.419355	0.580645

 

 

 

(2) 시도표 (time series plot) 를 그리기

 

pandas DataFrame 의 plot() 메소드를 사용하면 편리하게 시계열 도표를 그릴 수 있습니다. 이때 성별을 나타내는 칼럼 'M', 'F' 별로 선의 모양(line type)과 색깔(color) 을 style={'M': 'b--', 'F': 'r-'} 매개변수를 사용해서 다르게 해서 그려보겠습니다. ('M' 은 파란색 점선, 'F' 는 빨간색 실선)

 

df.div(df.sum(1), axis=0).plot(
    style={'M': 'b--', 'F': 'r-'}, 
    figsize=(12, 8), 
    title='Proportion Trend by Gender')

plt.show()

proportion trend plot by gender

 

[ Reference ]

* pandas.DataFrame.div(): https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.div.html

 

 

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

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

 

728x90
반응형
Posted by R Friend Rfriend

댓글을 달아 주세요

이번 포스팅에서는 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 R Friend Rfriend

댓글을 달아 주세요

  1. 파송송 2021.09.29 15:11  댓글주소  수정/삭제  댓글쓰기

    와...와...와...와...와...와...와...

    말이 안나오고 있어요. 웃음은 나오는데 말이 안나와요^^

    세상에나,,, 세상에나,,,, 이게 되네요 ㅎ

    천재 아니세요? 천재!

    항상 고맙습니다. RFriend님 덕에 진짜 이것까지 해보네요!

    너무너무 감사합니다~~~ 복 많이 받으세요!

  2. gamma 2021.10.01 22:05 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요?
    두 개의 DB를 rbind하기 위해서 두 개의 DB의 컬럼을 일치시키기 위해서
    컬럼을 추가하는 작업을 하고자 하는데요,
    아래에서 "반복문을 사용한 컬럼 추가"는 정상적으로 코드가 실행되는데,
    "반복문을 사용한 컬럼명 변경"은 안돼서
    개별적으로 직접 컬럼명 변경하는 코딩을 했는데,
    "반복문을 사용한 컬럼명 변경"을 할 수 있는 방법이 있는지요?


    row_count_경영체 <- nrow(안전성조사DB_농업경영체DB) #202,657, (row_count_경영체)*1
    col_count_경영체 <- ncol(안전성조사DB_농업경영체DB) #33


    # 아래 반복문을 수행하는 데, 13,375,362( = 202,657 X 33 X 2)개의 Cell에 대해서 작업을 하므로 약 20분 정도 소요됨.

    for(i in 1:row_count_경영체){
    for(j in 1:col_count_경영체){
    ifelse((안전성조사DB_농업경영체DB[i,j] == "")|(안전성조사DB_농업경영체DB[i,j] == "-"), 안전성조사DB_농업경영체DB[i,j] <- "NA", next)
    }
    }

    library(plyr)
    library(dplyr)

    융합DB_1차_결측치보정DB사용 <- read.csv("융합DB_1차_결측치보정DB사용_20210929.csv")
    융합DB_1차_결측치보정DB사용 <- 융합DB_1차_결측치보정DB사용[,-1]
    View(융합DB_1차_결측치보정DB사용)
    안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호 <- read.csv("안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호.csv")
    안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호 <- 안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호[,-c(1:2)]
    View(안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호)
    cols_융합DB_1차_결측치보정DB사용 <- colnames(융합DB_1차_결측치보정DB사용)
    # 46개
    cols_안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호 <- colnames(안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호)
    # 30개
    cols_diff_검출성분_융합DB_1차 <- setdiff(cols_안전성조사DB검출성분조회_2017_2020년_wi_NA_arranged_by_접수번호, cols_융합DB_1차_결측치보정DB사용)
    # 29개

    x
    1 의뢰기관
    2 분석기관
    3 의뢰일
    4 재배시도
    5 재배구군
    6 재배양식1
    7 재배양식2
    8 조사면적
    9 조사물량
    10 출하예정일
    11 분석일
    12 적부
    13 출하가능일
    14 조치내역2
    15 수출구분
    16 검출성분
    17 검출치
    18 허용기준
    19 반감기적용여부
    20 반감상수
    21 성분적부
    22 출하가능일자
    23 허용기준_수출
    24 적용내역
    25 적용기준
    26 농약상표명
    27 인증번호
    28 인증기관명
    29 농가명_농장명


    융합1차단순결합_컬럼추가 <- 융합DB_1차_결측치보정DB사용

    cell <- c("NA")
    c <- rep(cell, times = nrow(융합1차단순결합_컬럼추가))
    c_df <- as.data.frame(c) # View(c_df) # nrow(c_df) 262766행(row) ncol()

    #========================================================================================================================
    # 반복문을 사용한 컬럼 추가

    for(i in 1: nrow(as.data.frame(cols_diff_검출성분_융합DB_1차))){

    융합1차단순결합_컬럼추가[, paste0("c",i)] <- c_df
    }

    #========================================================================================================================
    # 반복문을 사용한 컬럼명 변경

    View(융합1차단순결합_컬럼추가)
    cols_diff_검출성분_융합DB_1차 <- as.data.frame(cols_diff_검출성분_융합DB_1차)
    for( i in 1:nrow(as.data.frame(cols_diff_검출성분_융합DB_1차))){
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, cols_diff_검출성분_융합DB_1차[i,1] = paste0("c",i))
    }

    #========================================================================================================================
    # 개별 직접 컬럼 추가

    융합1차단순결합_컬럼추가[, "c1"] <- c_df
    융합1차단순결합_컬럼추가[, "c2"] <- c_df
    융합1차단순결합_컬럼추가[, "c3"] <- c_df
    융합1차단순결합_컬럼추가[, "c4"] <- c_df
    융합1차단순결합_컬럼추가[, "c5"] <- c_df
    융합1차단순결합_컬럼추가[, "c6"] <- c_df
    융합1차단순결합_컬럼추가[, "c7"] <- c_df
    융합1차단순결합_컬럼추가[, "c8"] <- c_df
    융합1차단순결합_컬럼추가[, "c9"] <- c_df
    융합1차단순결합_컬럼추가[, "c10"] <- c_df
    융합1차단순결합_컬럼추가[, "c11"] <- c_df
    융합1차단순결합_컬럼추가[, "c12"] <- c_df
    융합1차단순결합_컬럼추가[, "c13"] <- c_df
    융합1차단순결합_컬럼추가[, "c14"] <- c_df
    융합1차단순결합_컬럼추가[, "c15"] <- c_df
    융합1차단순결합_컬럼추가[, "c16"] <- c_df
    융합1차단순결합_컬럼추가[, "c17"] <- c_df
    융합1차단순결합_컬럼추가[, "c18"] <- c_df
    융합1차단순결합_컬럼추가[, "c19"] <- c_df
    융합1차단순결합_컬럼추가[, "c20"] <- c_df
    융합1차단순결합_컬럼추가[, "c21"] <- c_df
    융합1차단순결합_컬럼추가[, "c22"] <- c_df
    융합1차단순결합_컬럼추가[, "c23"] <- c_df
    융합1차단순결합_컬럼추가[, "c24"] <- c_df
    융합1차단순결합_컬럼추가[, "c25"] <- c_df
    융합1차단순결합_컬럼추가[, "c26"] <- c_df
    융합1차단순결합_컬럼추가[, "c27"] <- c_df
    융합1차단순결합_컬럼추가[, "c28"] <- c_df
    융합1차단순결합_컬럼추가[, "c29"] <- c_df



    #========================================================================================================================
    # 개별 직접 컬럼명 변경

    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "의뢰기관" = "c1")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "분석기관" = "c2")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "의뢰일" = "c3")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "재배시도" = "c4")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "재배구군" = "c5")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "재배양식1" = "c6")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "재배양식2" = "c7")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "조사면적" = "c8")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "조사물량" = "c9")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "출하예정일" = "c10")

    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "분석일" = "c11")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "적부" = "c12")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "출하가능일" = "c13")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "조치내역2" = "c14")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "수출구분" = "c15")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "검출성분" = "c16")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "검출치" = "c17")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "허용기준" = "c18")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "반감기적용여부" = "c19")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "반감상수" = "c20")

    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "성분적부" = "c21")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "출하가능일자" = "c22")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "허용기준_수출" = "c23")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "적용내역" = "c24")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "적용기준" = "c25")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "농약상표명" = "c26")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "인증번호" = "c27")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "인증기관명" = "c28")
    융합1차단순결합_컬럼추가 <- rename(융합1차단순결합_컬럼추가, "농가명_농장명" = "c29")

    • R Friend Rfriend 2021.10.02 13:12 신고  댓글주소  수정/삭제

      안녕하세요 gamma님,

      dplyr 의 rename() 함수를 사용해서 변수명을 변경할 때 rename() 함수안에 기존변수이름과 신규변수이름을 매핑해서 콤마로 구분해서 연속으로 계속 써주면 됩니다. 아래 포스팅 참고하세요.

      https://rfriend.tistory.com/41

      반복문을 쓰면 DataFrame을 계속 호출하게 되므로 리소스를 계속 사용하게 되어서 추천하고 싶지는 않은데요, 그래도 꼭 반복문을 써야겠다면 plyr::rename() 으로 해서 해보실래요? 함수안의 변수명 인자값을 큰따옴표("변수이름") 형식으로 받기 때문에 반복문으로 해서 텍스트로 변수이름을 생성해도 인식할거 같아요.

  3. 파송송 2021.10.13 10:47  댓글주소  수정/삭제  댓글쓰기

    RFriend님, 안녕하시지요?
    덕분에 페이지 참고하면서 이것 저것 해보다가 또 궁금한 것이 생겼는데요,

    웹페이지 중에 링크가 있잖아요, 이걸 크롤링하면 텍스트로 가져오는데 아예 링크된 페이지의 내용까지도 텍스트로 긁어올 수도 있을까요?

    궁금해서 Stackoverlfow 같은 곳에 찾아보니 아직 해 본 사람이 없는 건지 내용은 없는데, why not? 이라는 생각이 계속 들어서 여쭤봐요~~ RFriend님 의견은 어떠신가요?

    • R Friend Rfriend 2021.10.13 13:23 신고  댓글주소  수정/삭제

      아래 처럼 해보실래요?
      댓글란에는 들여쓰기가 무시되므로 들여쓰기 주의해서 코드 사용하시구요.

      from BeautifulSoup import BeautifulSoup
      from urllib2 import urlopen

      html = urlopen("http://YourSiteAddress.com")
      soup = BeautifulSoup(html. read(), 'lxml')

      links = []
      for link in soup. find_all('a'):
      ----links. append(link. get('href'))

      이렇게 해서 href 링크 주소를 크롤링한 다음에, 이 링크 주소에 하나씩 방문해서 페이지 내용 크롤링해오면 됩니다.

    • 파송송 2021.10.14 01:01  댓글주소  수정/삭제

      와 진짜... Rfriend님, 혹시 직장이 막 국정원 이런데 아닌가요?
      이게 실현 가능했던 건가요? 저는 막연히 생각만 해봤는데, 진짜 가능할까 싶었거든요. 해외 사이트에도 해 본 사람도 없어서요..

      http://YourSiteAddress.com --> 요 사이트 들어가니 운영이 안되는 거라 일단 다른 사이트로 내일 해봐야겠어요.
      와~~ 빨리 내일이 왔으면 좋겠어요.
      뭐든 RFriend님한테 물어보면 진짜 다 되는군요!! ^^ 제가 너무 운이 좋은 거 같아요. 세상에 어쩜 살다보니 이런 천사분을 만나네요. 고맙습니다~

      PS) ----links 에서 ----- 이건 indent 맞죠? ㅎ

    • R Friend Rfriend 2021.10.14 01:06 신고  댓글주소  수정/삭제

      전 외국계 SW 회사 다니고 있어요.

      (1) urlopen() 의 괄호 안에 원하는 웹사이트 주소 넣어서 해보세요.

      (2) ----links 는 indentation 표기한 거예요.

      Good luck!

이전 포스팅에서는 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 R Friend Rfriend

댓글을 달아 주세요

  1. 뚝딱이 2021.09.19 01:45  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 파이썬에 대해서 검색하다가 이 포스트를 보게 되었습니다

    다름이아니라 저도 비슷하게 칼럼의 위치를 옮기는 거를 고민하다가 order함수를 만들었거든요ㅎ

    한번 시간이 되시면 봐주시면 감사하겠습니다ㅎ(https://blog.naver.com/sanzo213/222427768280)

    저의 경우

    어떤 칼럼(들) 맨 앞으로 보내기
    어떤 칼럼(들) 맨 뒤로 보내기
    어떤 칼럼(들) price칼럼 앞으로 보내기
    어떤 칼럼(들) rep78칼럼 뒤로 보내기

    를 초점을 맞추어서 order함수를 만들었습니다

    시간이 되시면 봐주시면 감사하겠습니다~ :)


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 R Friend 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 R Friend Rfriend

댓글을 달아 주세요