이번 포스팅은 두 개의 연속형 변수에 대한 관계를 파악하는데 유용하게 사용할 수 있는 산점도(Scatter Plot) 의 두번째 포스팅으로서 그룹별로 산점도의 점의 색깔과 모양을 다르게 하는 방법을 소개합니다. 

 

(1) 산점도 (Scatter Plot)

(2) 그룹별 산점도 (Scatter Plot by Groups)

(3) 산점도의 marker 크기 및 색깔, 모양 설정 (Setting Marker's size, color, shape)

(4) 산점도 행렬 (Scatter Plot Matrix)

 

 

산점도를 그리는데 사용할 데이터는 iris 로서, 'petal length'와 'petal width'의 연속형 변수에 대해서 'species' 그룹별로 점의 색깔과 모양을 다르게 설정해보겠습니다. 

 

참고로 species 에는 setosa 50개, versicolor 50개, virginica 50개씩의 관측치가 들어있습니다. 

 

 

 

# importing libraries

import numpy as np

import pandas as pd

 

import matplotlib.pyplot as plt

import seaborn as sns

plt.rcParams['figure.figsize'] = [10, 8] # setting figure size

 

 

 

# loading 'iris' dataset from seaborn

iris = sns.load_dataset('iris')

iris.shape

(150, 5)

 

iris.head()

  sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa

 

iris.groupby('species').size()

species setosa 50 versicolor 50 virginica 50 dtype: int64

 

 

 

 

  (1) matplotlib 으로 그룹별 산점도 그리기 (scatter plot by groups via matplotlib)

 

 

# Scatter plot with a different color by groups

groups = iris.groupby('species')

 

fig, ax = plt.subplots()

for name, group in groups:

    ax.plot(group.petal_length, 

            group.petal_width, 

            marker='o', 

            linestyle='',

            label=name)

ax.legend(fontsize=12, loc='upper left') # legend position

plt.title('Scatter Plot of iris by matplotlib', fontsize=20)

plt.xlabel('Petal Length', fontsize=14)

plt.ylabel('Petal Width', fontsize=14)

plt.show()

 

 

 

 

  (2) seaborn 으로 그룹별 산점도 그리기 (scatter plot by groups via seaborn)

 

코드가 깔끔하고 가독성이 좋으며, 산점도 그래프도 보기에 참 좋습니다. 

 

 

# Scatter plot by Groups

sns.scatterplot(x='petal_length', 

                y='petal_width', 

                hue='species', # different colors by group

                style='species', # different shapes by group

                s=100, # marker size

                data=iris)

plt.show()

 

 

 

 

혹시 AttributeError: module 'seaborn' has no attribute 'scatterplot' 에러가 나면 seaborn version upgrade를 해주시기 바랍니다. 

 

 

# error: AttributeError: module 'seaborn' has no attribute 'scatterplot' 

# solution: upgrade seaborn

pip install --upgrade seaborn

 

 

 

 

  (3) pandas로 그룹별 산점도 그리기 (scatter plot by groups via pandas)

 

# adding 'color' column

iris['color'] = np.where(iris.species == 'setosa', 'red', 

                         np.where(iris.species =='versicolor', 

                         'green', 

                         'blue'))

 

# scatter plot

iris.plot(kind='scatter',

          x='petal_length', 

          y='petal_width', 

          s=50, # marker size

          c=iris['color']) # marker color by group

 

plt.title('Scatter Plot of iris by pandas', fontsize=20)

plt.xlabel('Petal Length', fontsize=14)

plt.ylabel('Petal Width', fontsize=14)

plt.show()

 

 

 

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

이번 포스팅이 도움이 되었다면 아래의 '공감~

'를 꾹 눌러주세요. ^^

 

다음번 포스팅에서는 산점도의 marker 크기 및 색깔, 모양 설정 (Setting Marker's size, color, shape) 에 대해서 소개하겠습니다. 

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅부터는 두 개의 연속형 변수에 대한 관계를 파악하는데 유용하게 사용할 수 있는 산점도(Scatter Plot) 를 4번에 나누어서 소개를 하겠습니다. 

 

(1) 산점도 (Scatter Plot)

(2) 그룹별 산점도 (Scatter Plot by Groups)

(3) 산점도의 marker 크기 및 색깔, 모양 설정 (Setting Marker's size, color, shape)

(4) 산점도 행렬 (Scatter Plot Matrix)

 

 

기본적인 산점도를 matplotlib, seaborn, pandas 패키지를 사용하여 순서대로 그려보겠습니다. 

 

사용할 데이터는 iris 데이터셋의 'petal length'와 'petal width'의 두 개 연속형 변수입니다. 

 

 

# importing libraries

import numpy as np

import pandas as pd

 

import matplotlib.pyplot as plt

import seaborn as sns

plt.rcParams['figure.figsize'] = [12, 8] # setting figure size

 

 

 

# loading 'iris' dataset from seaborn

iris = sns.load_dataset('iris')

iris.shape

(150, 5) 

 

iris.head()

  sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa

 

 

 

  (1) matplotlib을 사용한 산점도 (scatter plot by matplotlib)

 

# Basic Scatter Plot

plt.plot('petal_length',  # x

         'petal_width',  # y

         data=iris, 

         linestyle='none', 

         marker='o', 

         markersize=10,

         color='blue', 

         alpha=0.5)

plt.title('Scatter Plot of iris by matplotlib', fontsize=20)

plt.xlabel('Petal Length', fontsize=14)

plt.ylabel('Petal Width', fontsize=14)

plt.show()

 

 

 

산점도에 X, Y 좌표를 사용하여 직사각형(rectangle), 원(circle), 직선(line)을 추가하여 보겠습니다. 먼저 matplotlib.patches 를 importing 해주어야 하고, 산점도를 그린 다음에, add_patch() 함수를 사용하여 직사각형, 원, 직선을 추가합니다. 

 

 

# adding a rectangle, a circle

import matplotlib.patches as patches

import matplotlib.pyplot as plt

 

fig1 = plt.figure()

ax1 = fig1.add_subplot(111)

 

# (0) scatter plot

ax1.plot('petal_length', 'petal_width', data=iris, linestyle='none', marker='o')

 

# (1) adding a rectangle

ax1.add_patch(

    patches.Rectangle(

        (3, 1), # (x, y)

        2, # width

        1, # height

        alpha=0.2, 

        facecolor="blue", 

        edgecolor="black", 

        linewidth=2, 

        linestyle="solid", 

        angle=-10))

 

# (2) adding a circle

ax1.add_patch(

    patches.Circle(

        (1.5, 0.25), # (x, y)

        0.5, # radius

        alpha=0.2, 

        facecolor="red", 

        edgecolor="black", 

        linewidth=2, 

        linestyle='solid'))

 

# (3) adding a line

plt.plot([4, 6], [2.2, 1.1], color="green", lw=4, linestyle='solid')

 

plt.title("Adding a Rectangle, a Circle and a Line", fontsize=20)

plt.xlabel('Petal Length', fontsize=14)

plt.ylabel('Petal Width', fontsize=14)

plt.show()

 

 

 

 

 

 

 

  (2) seaborn을 사용한 산점도 (scatter plot by seaborn)

 

seaborn 패키지의 (a) regplot() 함수와 (b) scatterplot() 함수를 사용해서 산점도를 그릴 수 있습니다. 순서대로 소개합니다. 

 

(a) regplot() 함수를 사용한 산점도

 

선형회귀 적합 선을 포함시키지 않으려면 fit_reg=False 를 설정해주면 됩니다. 

 

 

# Basic Scatter Plot by seaborn

sns.regplot(x=iris['petal_length'], 

           y=iris['petal_width'], 

           fit_reg=False) # no regression line

plt.title('Scatter Plot of iris by regplot()', fontsize=20)

plt.xlabel('Petal Length', fontsize=14)

plt.ylabel('Petal Width', fontsize=14)

plt.show()

 

 

 

 

두 연속형 변수 간의 선형회귀 적합선을 산점도에 포함시키려면 fit_reg=True 를 설정해주면 됩니다. (defalt 이므로 별도로 표기를 해주지 않아도 회귀적합선이 추가됩니다)

 

 

# Scatter Plot with regression line by seaborn regplot()

sns.regplot(x=iris['petal_length'], 

           y=iris['petal_width'], 

           fit_reg=True) # default

plt.title('Scatter Plot with Regression Line by regplot()', fontsize=20)

plt.show()

 

 

 

X축과 Y축의 특정 값의 조건을 기준으로 산점도 marker의 색깔을 다르게 해보겠습니다. 가령, 'petal length' > 2.5 & 'petal width' > 0.8 이면 '빨간색', 그 이외는 '파란색'으로 설정을 해보겠습니다. 조건에 맞게 'color'라는 새로운 변수를 생성한 후에, scatter_kws={'facecolors': iris_df['color']}) 로 조건별 색을 설정하는 방법을 사용하였습니다. 

 

 

# Control color of each marker based on X and Y values

iris_df = iris.copy()

 

# Adding a 'color' column based on x and y values

cutoff = (iris_df['petal_length']>2.5) & (iris_df['petal_width'] > 0.8)

iris_df['color'] = np.where(cutoff==True, "red", "blue")

 

# Scatter Plot with different colors based on X and Y values

sns.regplot(x=iris['petal_length'], 

           y=iris['petal_width'], 

           fit_reg=False

           scatter_kws={'facecolors': iris_df['color']}) # marker color

plt.title('Scatter Plot with different colors by X & Y values', fontsize=20)

plt.show()

 

 

 

 

(b) scatterplot() 함수를 사용한 산점도

 

# scatter plot by seaborn scatterplot()

ax = sns.scatterplot(x='petal_length', 

                     y='petal_width', 

                     alpha=0.5,

                     data=iris)

plt.title('Scatter Plot by seaborn', fontsize=20)

plt.show()

 

 

 

 

  (3) pandas를 사용한 산점도 (scatter plot by pandas)

 

 

iris.plot.scatter(x='petal_length', 

                  y='petal_width', 

                  s=50, # marker size

                  c='blue', 

                  alpha=0.5)

plt.title('Scatter Plot of iris by pandas', fontsize=20)

plt.xlabel('Petal Length', fontsize=14)

plt.ylabel('Petal Width', fontsize=14)

plt.show()

 

 

 

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

 

이번 포스팅이 도움이 되었다면 아래의 '공감~

'를 꾹 눌러주세요. ^^

 

다음번 포스팅에서는 그룹별 산점도 (Scatter Plot by Groups) 의 마커 모양, 색깔을 다르게 그리는 방법을 소개하겠습니다. 

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 시장 점유율과 같이 구성비율을 시각화하는데 사용하는 (1) 원 그래프 (Pie Chart), (2) 하위 그룹을 포함한 도넛 그래프 (Donut Chart with Subgraphs) 그리는 방법을 소개하겠습니다. 


두 개 모두 matplotlib 의 pie() 함수를 사용합니다. 



  (1) 원 그래프 (Pie Chart)



# importing library and set figure size

import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = [12, 8]

 



# information of groups

group_names = ['Group_A', 'Group_B', 'Group_C']

group_sizes = [95, 54, 25]

group_colors = ['yellowgreen', 'lightskyblue', 'lightcoral']

group_explodes = (0.1, 0, 0) # explode 1st slice

 



# Pie chart

plt.pie(group_sizes, 

        explode=group_explodes, 

        labels=group_names, 

        colors=group_colors, 

        autopct='%1.2f%%', # second decimal place

        shadow=True, 

        startangle=90,

        textprops={'fontsize': 14}) # text font size

plt.axis('equal') #  equal length of X and Y axis

plt.title('Pie Chart of Market Share', fontsize=20)

plt.show()




  (2) 하위 그룹을 포함한 도넛 그래프 (Donut Chart with Subgraphs)


상위 그룹 A, B, C가 있고, 각 상위 그룹에 딸린 하위 그룹으로 'A_1'~'A_4', 'B_1'~'B_3', 'C_1'~'C_2' 의 하위 그룹(subgroups)이 있는 경우 가운데가 뚫린 도넛 모양의 도넛 그래프로 표현해주면 효과적입니다. 


기본 원리는 간단합니다. matplotlib 으로 원 그래프를 그리되, radius 와 width 의 숫자를 적절히 조절하여 (a) 바깥쪽의 상위 그룹의 원 그래프를 반지름이 크고 폭은 작아서 가운데가 뚫린 도넛 그래프로 만들고, (b) 안쪽에는 하위 그룹의 원 그래프를 반지름이 상위 그룹의 것보다는 작고(즉, 상위 그룹 도넛 그래프의 폭 만큼을 빼줌) 폭은 상위 그룹과 같은 가운데가 뚫린 도넛 그래프를 만들여서, (a)와 (b)의 두개 도넛 그래프를 겹치게 그리는 것입니다. 



# importing library and set figure size

import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = [12, 8]

 



 # info. of groups

group_names = ['Group_A', 'Group_B', 'Group_C']

group_sizes = [95, 54, 25]


# info. of subgroups

subgroup_names = ['A_1', 'A_2', 'A_3', 'A_4', 

                  'B_1', 'B_2', 'B_3', 

                  'C_1', 'C_2']

subgroup_sizes = [50, 30, 10, 5, 30, 20, 4, 20, 5]


# colors

a, b, c = [plt.cm.Reds, plt.cm.Greens, plt.cm.Blues]


# width

width_num = 0.4

 



# Outside Ring

fig, ax = plt.subplots()

ax.axis('equal')

pie_outside, _ = ax.pie(group_sizes, 

                        radius=1.3

                        labels=group_names, 

                        labeldistance=0.8,

                        colors=[a(0.6), b(0.6), c(0.6)])

plt.setp(pie_outside

         width=width_num

         edgecolor='white')


# Inside Ring

pie_inside, plt_labels, junk = \

    ax.pie(subgroup_sizes, 

           radius=(1.3 - width_num), 

           labels=subgroup_names, 

           labeldistance=0.75, 

           autopct='%1.1f%%', 

           colors=[a(0.5), a(0.4), a(0.3), a(0.2), 

                   b(0.5), b(0.4), b(0.3), 

                   c(0.5), c(0.4)])

plt.setp(pie_inside

         width=width_num

         edgecolor='white')

plt.title('Donut Plot with Subgroups', fontsize=20)

plt.show()

 



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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,

지난 포스팅에서는 Python으로 연속형 자료에 대해 히스토그램박스 그래프를 그리는 방법을 소개하였습니다. 


이번 포스팅에서는 이산형 자료에 대해 범주(category), 계급(class)별로 빈도나 합계 등을 막대로 그려서 비교하는데 유용하게 사용하는 막대 그래프(bar chart)를 Python의 matplotlib, seaborn, pandas를 사용하여 그리는 방법을 소개하겠습니다. 


예제로 사용할 데이터셋은 seaborn에 내장되어 있는 tips 데이터셋입니다. 



# importing packages

import numpy as np

import pandas as pd


import matplotlib.pyplot as plt

import seaborn as sns

plt.rcParams['figure.figsize'] = [10, 6]

 



# Loading 'tips' dataset from seaborn

tips = sns.load_dataset('tips')

tips.shape

(244, 7)

 

tips.head()

total_billtipsexsmokerdaytimesize
016.991.01FemaleNoSunDinner2
110.341.66MaleNoSunDinner3
221.013.50MaleNoSunDinner3
323.683.31MaleNoSunDinner2
424.593.61FemaleNoSunDinner

4





  (1) matplotlib으로 막대 그리프 그리기 (bar chart by matplotlib)


tips DataFrame에서 요일(day)별로 tip 의 합계를 구해서 막대 그래프로 비교를 해보겠습니다. 



# Summary Statistics

tips_sum_by_day = tips.groupby('day').tip.sum()

tips_sum_by_day

day
Thur    171.83
Fri      51.96
Sat     260.40
Sun     247.39 

Name: tip, dtype: float64

 



label = ['Thur', 'Fri', 'Sat', 'Sun']

index = np.arange(len(label))

 



# Basic Bar Chart

plt.bar(index, tips_sum_by_day)

plt.title('Sum of Tips by Day', fontsize=20)

plt.xlabel('Day', fontsize=18)

plt.ylabel('Sum of Tips', fontsize=18)

plt.xticks(index, label, fontsize=15)

plt.show()

 




막대 그래프의 막대 색깔(bar color)과 투명도(transparency, alpha)를 조절해보겠습니다. 



# bar color, transparency

plt.bar(label, tips_sum_by_day, 

        color='red', # color

        alpha=0.5) # transparency

plt.show()

 





막대그래프의 막대 폭(width)을 좀더 좁게하고, 가운데 정렬 대신에 한쪽 끝으로 정렬(align)을 변경해보겠습니다. 



# bar width, align

plt.bar(label, tips_sum_by_day, 

        width=0.5, # default: 0.8

        align='edge') # default: 'center'

plt.show()

 




X축의 라벨이 갯수가 많거나 길이가 길 경우에는 X 축 라벨을 비스듬히 눕히거나 90도로 세우는 것이 보기에 더 효과적일 때가 있습니다. X축 라벨을 90도 회전시켜 보겠습니다.  



# X tick labels rotation

plt.bar(index, tips_sum_by_day)

plt.xlabel('Day', fontsize=18)

plt.xticks(index, label, fontsize=15, 

           rotation=90) # when X tick labels are long

plt.show()

 




X축의 라벨 개수가 많다면 막대그래프를 아예 가로로 눕혀서 그리는 것이(horizontal bar chart) 보기에 효과적일 때도 있습니다. 옆으로 누운 막대그래프는 plt.barh() 함수를 사용합니다. 



# Horizontal Bar Chart

plt.barh(index, tips_sum_by_day)

plt.title('Sum of Tips by Day', fontsize=18)

plt.ylabel('Day', fontsize=15)

plt.xlabel('Sum of Tips', fontsize=15)

plt.yticks(index, label, fontsize=13, rotation=0)

plt.show()




2개의 범주형 변수를 사용하여 각 클래스별로 빈도나 합계를 막대그래프로 비교하려면 (a) 위로 쌓은 막대그래프 (stacked bar chart)나, (b) 옆으로 나란히 놓은 막대그래프 (dodged bar chart)를 사용합니다. 


먼저, (a) 요일(day)과 성별(sex)의 두개 범주형 변수의 클래스별로 팁(tip)의 합계를 비교하기 위해 위로 쌓은 막대그래프 (stacked bar chart)를 그려보겠습니다. 



# summary by group

tips_sum_by_day_male = tips[tips['sex'] == 'Male'].groupby('day').tip.sum()

tips_sum_by_day_female = tips[tips['sex'] == 'Female'].groupby('day').tip.sum()


print('--Male--')

print(tips_sum_by_day_male);

print(' ')

print('--Female--')

print(tips_sum_by_day_female);

--Male--

day
Thur     89.41
Fri      26.93
Sat     181.95
Sun     186.78
Name: tip, dtype: float64
 
--Female--
day
Thur    82.42
Fri     25.03
Sat     78.45
Sun     60.61
Name: tip, dtype: float64




# Bar Chart by 2 categorical variables

# Stacked Bar Chart

label = ['Thur', 'Fri', 'Sat', 'Sun']

N = len(tips['day'].unique())

index = np.arange(N)

alpha = 0.5


p1 = plt.bar(index, tips_sum_by_day_male, color='b', alpha=alpha)

p2 = plt.bar(index, tips_sum_by_day_female, color='r', alpha=alpha,

             bottom=tips_sum_by_day_male) # stacked bar chart

plt.title('Stacked Bar Chart of Sum of Tips by Day & Sex', fontsize=20)

plt.ylabel('Sum of Tips', fontsize=18)

plt.xlabel('Day', fontsize=18)

plt.xticks(index, label, fontsize=15)

plt.legend((p1[0], p2[0]), ('Male', 'Female'), fontsize=15)

plt.show()

 




다음으로 (b) 요일(day)과 성별(sex)의 두개 범주형 변수의 클래스별로 팁(tip)의 합계를 비교하기 위해 옆으로 나란히 놓은 막대그래프를 그려보겠습니다. 두번째 막대 그래프에서 X축의 위치를 bar_width 만큼 오른쪽으로 바로 붙여서 그려지도록 index+bar_width 로 설정(빨간색 부분)해주면 됩니다. 



# Dodged Bar Chart (with same X coordinates side by side)

bar_width = 0.35

alpha = 0.5


p1 = plt.bar(index, tips_sum_by_day_male, 

             bar_width, 

             color='b', 

             alpha=alpha,

             label='Male')

p2 = plt.bar(index + bar_width, tips_sum_by_day_female, 

             bar_width, 

             color='r', 

             alpha=alpha,

             label='Female')


plt.title('Dodged Bar Chart of Sum of Tips by Day & Sex', fontsize=20)

plt.ylabel('Sum of Tips', fontsize=18)

plt.xlabel('Day', fontsize=18)

plt.xticks(index, label, fontsize=15)

plt.legend((p1[0], p2[0]), ('Male', 'Female'), fontsize=15)

plt.show()

 




  (2) seaborn 으로 막대 그래프 그리기 (bar chart by seaborn)


seaborn 패키지를 사용하여 위의 matplotlib 으로 그린 stacked bar chart를 그려보겠습니다. pandas 의 df.plot(kind='bar', stacked=True) 함수처럼 stacked=True 옵션이 seaborn 에는 없는 것 같습니다 (제가 못찾은 걸 수도 있구요). 그래서 workaround 로서 DataFrame에 'day'와 'sex' 기준으로 정렬을 한 후에 누적 합(cumulative sum) 을 구해서, 누적합 칼럼에 대해 dodge=False 로 하여 서로 겹쳐그리게끔 해서 위로 쌓은 누적 막대그래프 (stacked bar plot by seaborn)를 그렸습니다.  (3)번의 pandas 대비 많이 복잡합니다. (댓글에 질문 남겨주신분께 감사드립니다. 덕분에 제가 잘못 알고 쓴 부분 수정할 수 있었습니다.)



tips_sum_by_day_sex = pd.DataFrame(tips.groupby(['day', 'sex']).tip.sum())

tips_sum_by_day_sex = tips_sum_by_day_sex.reset_index()

tips_sum_by_day_sex

daysextip
0ThurMale89.41
1ThurFemale82.42
2FriMale26.93
3FriFemale25.03
4SatMale181.95
5SatFemale78.45
6SunMale186.78
7SunFemale60.61



# sort dataframe by 'day' and 'sex' in descending order first

tips_sum_by_day_sex = tips_sum_by_day_sex.sort_values(by=['day', 'sex'], ascending=False)

tips_sum_by_day_sex

daysextip
7SunFemale60.61
6SunMale186.78
5SatFemale78.45
4SatMale181.95
3FriFemale25.03
2FriMale26.93
1ThurFemale82.42
0ThurMale89.41


# then, calculate cumulative summation by 'day' group

tips_sum_by_day_sex['tip_cumsum'] = tips_sum_by_day_sex.groupby(['day'])['tip'].cumsum(axis=0)

tips_sum_by_day_sex

daysextiptip_cumsum
7SunFemale60.6160.61
6SunMale186.78247.39
5SatFemale78.4578.45
4SatMale181.95260.40
3FriFemale25.0325.03
2FriMale26.9351.96
1ThurFemale82.4282.42
0ThurMale89.41171.83


# atfer that, sort agian by 'day' and 'sex' in ascending order

tips_sum_by_day_sex = tips_sum_by_day_sex.sort_values(by=['day', 'sex'], ascending=True)

tips_sum_by_day_sex

daysextiptip_cumsum
0ThurMale89.41171.83
1ThurFemale82.4282.42
2FriMale26.9351.96
3FriFemale25.0325.03
4SatMale181.95260.40
5SatFemale78.4578.45
6SunMale186.78247.39
7SunFemale60.6160.61




아래 sns.barplot() 함수에서 y 값에 누적 합 칼럼 (y='tip_cumsum') 을 사용한 점, dodge=False 로 옵션 설정한 점 유의하세요. 



# Stacked Bar Chart

sns.barplot(x='day', y='tip_cumsum', hue='sex', data=tips_sum_by_day_sex, 

           dodge=False) # stacked bar chart

plt.title('Stacked Bar Chart by Seaborn', fontsize='20')

plt.show()







seaborn으로 옆으로 나란히 두 개의 범주형 변수의 클래스별 막대그래프를 그려서 비교를 하려면 dodge=True (default setting) 을 해주면 됩니다. 



# Dodged Bar Chart

sns.barplot(x='day', y='tip', hue='sex', data=tips_sum_by_day_sex) # default : dodge=True

plt.title('Dodged Bar Chart by Seaborn', fontsize=20)

plt.legend(fontsize=12)

plt.show()





  (3) pandas 로 막대 그래프 그리기 (bar chart by pandas)


pandas로도 막대 그래프를 그릴 수 있는데요, 위의 matplotlib, seaborn으로 그릴 때 사용했던 데이터 집계 테이블 형태가 조금 다르므로 유심히 살펴보고 참고해서 사용하시기 바랍니다. 



# make a DataFrame

tips_sum_by_day = pd.DataFrame(tips.groupby('day').tip.sum())

tips_sum_by_day = tips_sum_by_day.reset_index()

daytip
0Thur171.83
1Fri51.96
2Sat260.40
3Sun247.39

 




pandas DataFrame에 df.plot.bar() 함수를 사용하거나 df.plot(kind='bar') 를 사용해서 막대그래프를 그립니다.  rot 는 X축 라벨의 회전(rotation) 각도를 설정하는데 사용합니다. 



# basic bar chart with a single column

tips_sum_by_day.plot.bar(x='day', y='tip', rot=0)

plt.show()





이번에는 pandas로 요일(day)과 성별(sex)의 두개 범주형 변수의 각 클래스별로 팁의 합계(summation of tip)를 집계하여 위로 쌓은 막대그래프(stacked bar chart)를 그려보겠습니다. 



# making a DataFrame with 'day' and 'sex' groups

tips_sum_by_day_sex = pd.DataFrame(tips.groupby(['day', 'sex']).tip.sum())

tips_sum_by_day_sex = tips_sum_by_day_sex.reset_index()


# pivot

tips_sum_by_day_sex_pivot = \

    tips_sum_by_day_sex.pivot(index='day', 

                              columns='sex', 

                              values='tip')

tips_sum_by_day_sex_pivot

sexMaleFemale
day
Thur89.4182.42
Fri26.9325.03
Sat181.9578.45
Sun186.7860.61




# Stacked Bar Chart by pandas

tips_sum_by_day_sex_pivot.plot.bar(stacked=True, rot=0)

plt.title('Stacked Bar Chart by Pandas', fontsize=20)

plt.show()




이번에는 pandas로 요일(day)별 성별(sex) 팁의 합계를 옆으로 나란히 막대그래프(dodged bar chart by pandas)로 그려서 비교를 해보겠습니다. 



# Dodged Bar Chart by pandas

tips_sum_by_day_sex_pivot.plot(kind='bar', rot=0)

plt.title('Dodged Bar Chart by Pandas', fontsize=20)

plt.legend(fontsize='12')

plt.show()

 



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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. 



728x90
반응형
Posted by Rfriend
,

탐색적 데이터 분석 단계에서 변수의 분포, 중심 경향, 퍼짐 정도, 치우침 정도 등을 한눈에 살펴볼 수 있는 시각화 종류로 히스토그램이 많이 사용됩니다. 


이번 포스팅에서는 Python의 matplotlib.pyplot, seaborn, pandas를 이용해서 하나의 변수, 하나의 그룹에 대한 히스토그램(Histogram)을 그리는 방법을 소개하겠습니다. 


그리고 다음번 포스팅에서는 여러개의 변수, 여러개의 그룹별 히스토그램을 그리는 방법을 다루어보겠습니다. 


필요한 패키지를 import하고 데이터셋을 loading하겠습니다. 

예제로 사용할 데이터는 seaborn 패키지에 들어있는 iris 데이터세입니다. setosa, versicolor, virginica 종별로 50개씩, 총 150개의 붖꽃 관측치에 대해서 꽃받침(sepal)과 꽃입(petal)의 길이와 넓이를 측정한 자료입니다. 기계학습 공부할 때 약방의 감초처럼 분류나 군집분석 예제로 사용되곤 하는 바로 그 데이터셋입니다. 



import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import seaborn as sns


# Data Loading

iris = sns.load_dataset('iris')

iris.shape

(150, 5)

iris.head()
sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa


iris.groupby('species').size()

species
setosa        50
versicolor    50
virginica     50
dtype: int64

 




  (1) matplotlib.pyplot 으로 히스토그램 그리기


plt.hist() 함수에 X변수의 데이터와 bin의 개수를 입력해주면 됩니다. 이렇 히스토그램을 그리면 그래프 뿐만 아니라 아래처럼 2개의 array와 <a list of Patch objects>가 같이 반환됩니다. 


히스토그램을 그릴 때는 bin의 개수를 적당히(?) 설정하는 것이 매우 중요합니다. bin 개수가 너무 적으면 분포가 뭉뚱그려지며, bin 개수가 너무 많으면 이빨빠지 빗처럼 보기에 이상해집니다. 대개의 경우 bins 값을 입력하지 않은채로 default 세팅으로 해서 그래프를 그려도 제법 보기에 좋게 나오는데요, 혹시 마음에 들지 않는다면 bins=x 값을 변경해가면서 여러번 시도를 해보시기 바랍니다. 



plt.hist(iris['sepal_width'], bins=10)



히스토그램만 보고 싶을 때는 아래처럼 n, bins, patches = plt.hist() 처럼 관측치 값, bin 개수, patch 객체를 n, bins, patches 에 별도로 할당해주면 됩니다. 



n, bins, patches = plt.hist(iris['sepal_width'], bins=10)

 


n, bins, patches

(array([ 4.,  7., 22., 24., 37., 31., 10., 11.,  2.,  2.]),
 array([2.  , 2.24, 2.48, 2.72, 2.96, 3.2 , 3.44, 3.68, 3.92, 4.16, 4.4 ]),
 <a list of 10 Patch objects>)





Y축을 빈도수(frequency)가 아니라 density로 하고 싶을 때는 density=True 를 설정해주면 됩니다. 



# Y axis as density

n, bins, patches = plt.hist(iris['sepal_width'], bins=10, density=True)





히스토그램의 색깔은 facecolor = 'blue' 식으로 설정해주며, alpha 는 투명도(0~1)를 조절할 때 사용합니다. alpha 가 0에 가까워질수록 투명해집니다. 



# facecolor, alpha

n, bins, patches = plt.hist(iris['sepal_width'], bins=10, 

                            density=True, 

                            facecolor='blue'

                            alpha=0.5)




X축과 Y축 이름은 plt.xlabel(), plt.ylabel() 함수로 지정하며, 제목은 plt.title()를 사용하여 추가할 수 있습니다. X축과 Y축의 범위를 강제로 지정해주고 싶으면 plt.axis(X축 시작, X축 끝, Y축 시작, Y축 끝)의 순서대로 값을 입력해줍니다. 



# Setting X, Y label and Title, axis

n, bins, patches = plt.hist(iris['sepal_width'], 

                            bins=10, 

                            density=True, 

                            facecolor='blue', 

                            alpha=0.5)

plt.xlabel('X bins')

plt.ylabel('Density')

plt.title('Histogram of Sepal Width')

plt.axis([1.5, 4.5, 0, 1.1])

plt.show()




  (2) seaborn 패키지로 히스토그램 그리기


seaborn 패키지의 distplot() 함수로 히스토그램을 그리니 density 기준의 히스토그램에 kernel density curve가 겹쳐져서 그래프가 그려졌습니다. 디폴트 세팅으로 그렸는데 bin 개수도 적당해보이고, 분포 곡선까지 겹쳐서 그려주니 편리하고 좋네요. (density curve와 겹쳐그려야 하므로 히스토그램은 frequency가 아니라 density 기준입니다.) 



sns.distplot(iris['sepal_width'])

plt.show()

 




물론 세부 옵션 설정을 통해서 그래프를 자유자재로 그릴 수 있습니다. 옵션 이름만 보면 무슨 기능인지 짐작할 수 있으므로 부연설명은 생략하겠습니다. 


kde=True 에서 kde는 Kernel Density Estimate 를 하여 커널 밀도 함수 곡선을 그리라는 뜻입니다. 


sns.distplot()에서 반환된 axis를 사용해서 seaborn histogram의 제목(title), X축 이름(X axis label), Y축 이름(Y axis label)을 설정할 수 있습니다. (ax.set_title(), ax.set_xlabel(), ax.set_ylabel())



# Kernel Density Curve with histogram

ax = sns.distplot(iris['sepal_width'], 

                      hist=True, 

                      kde=True, 

                      bins=10, 

                      color='blue', 

                      hist_kws={'edgecolor': 'gray'}, 

                      kde_kws={'linewidth': 2})

ax.set_title('Histogram of Sepal Width')

ax.set_xlabel('Sepal Width(cm)')

ax.set_ylabel('Density')

plt.show()




Kernel Density Curve만 그리고 싶다면 sns.distplot(x, hist=False) 라고 해서 그려도 되는데요, 좀더 많은 옵션을 사용하고 싶으면 아래처럼 sns.kdeplot() 함수를 사용하면 편리합니다. 



# Kernel Density Estimate Plot

sns.kdeplot(iris['sepal_width'],  

               shade=True, 

               bw=2, 

               label="Sepal Width")

plt.legend()

plt.show()





  (3) pandas.DataFrame.hist 를 이용한 히스토그램 그리기


pandas의 DataFrame에 아래처럼 바로 hist() 함수를 사용해서 히스토그램을 그릴 수 있습니다. matplotlib.pyplot.hist() 함수를 pandas가 가져다가 히스토그램을 그려주므로 (1)번의 plt.hist() 의 결과와 동일하게 나왔습니다. (디폴트 세팅은 grid=True 여서 격자로 그리드가 쳐져 있음)



# Histogram by pandas.DataFrame.hist

iris['sepal_width'].hist(bins=10, grid=False)

plt.show()



저는 개인적으로는 seaborn 의 그래프가 제일 이뻐보이기는 한데요, 여러 책의 예제 코드로 가장 많이 눈에 띄이는 것은 matplotlib.pyplot 이네요. 간단하게 쓰기에는 pandas.DataFrame.hist 가 손이 제일 덜 가구요. 


다음번 포스팅에서는 여러개의 변수/그룹에 대한 히스토그램 그리는 방법을 소개하겠습니다. 


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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요.  :-)



728x90
반응형
Posted by Rfriend
,

여러번에 걸친 지난 포스팅에서는 groupby()와 apply()를 이용하여 그룹 별로 통계량을 구한다든지, 결측값을 대체한다든지, 변수 간 상관계수를 구하는 방법을 소개하였습니다.  


이번 포스팅에서는 groupby()와 apply()를 사용하여 그룹 별로 선회회귀모형을 적합(Group-wise Linear Regression)하는 방법을 소개하겠습니다. 


그룹 개수가 많고, 그룹별로 회귀계수를 비교하고자 할 때 이번 포스팅을 참고하면 그룹별로 일일이 하나씩 모형을 적합하지 않고도 짧은 코드로 간편하게 그룹별 회귀모형을 적합할 수 있습니다. 



[ 그룹 별 선형회귀모형 적합 (Group-wise Linear Regression) ]




예제에 사용할 diabetes (당뇨병) 데이터와 선형회귀모형을 적합할 때 사용할 sklearn의 linear_model 모듈을 importing 하겠습니다. 


당뇨병 환자 442명의 'age', 'sex', bmi', bp', 's1', 's2', 's3', 's4', 's5', 's6' 등의 10개 설명변수와, 우리가 예측하고자 하는 'target' 반응변수가 있는 데이터셋입니다. 설명변수는 표준화가 되어있군요. 



import numpy as np

import pandas as pd

from sklearn import datasets, linear_model


diabetes = datasets.load_diabetes()


diabetes.DESCR 

'Diabetes dataset
================
Notes
-----
Ten baseline variables, age, sex, body mass index, average blood

npressure, and six blood serum measurements were obtained for each of n =442 diabetes patients, as well as the response of interest, a\nquantitative measure of disease progression one year after baseline.\n\nData Set Characteristics:\n\n :Number of Instances: 442\n\n :Number of Attributes: First 10 columns are numeric predictive values\n\n :Target: Column 11 is a quantitative measure of disease progression one year after baseline\n\n :Attributes:\n :Age:\n :Sex:\n :Body mass index:\n :Average blood pressure:\n :S1:\n :S2:\n :S3:\n :S4:\n :S5:\n :S6:\n\nNote: Each of these 10 feature variables have been mean centered and scaled by the standard deviation times `n_samples` (i.e. the sum of squares of each column totals 1).\n\nSource URL:\nhttp://www4.stat.ncsu.edu/~boos/var.select/diabetes.html\n\nFor more information see:\nBradley Efron, Trevor Hastie, Iain Johnstone and Robert Tibshirani (2004) "Least Angle Regression," Annals of Statistics (with discussion), 407-499.\n(http://web.stanford.edu/~hastie/Papers/LARS/LeastAngle_2002.pdf)\n'


diabetes.feature_names

['age', 'sex', 'bmi', 'bp', 's1', 's2', 's3', 's4', 's5', 's6']

diabetes.target[:5]
array([151.,  75., 141., 206., 135.])




이번 포스팅이 '그룹'별 선형회귀모형 분석이므로 'sex(성별)'을 'M (Male)', 'F (Female)' 의 2개 그룹으로 나누어서 'age', 'bmi' 의 2개의 설명변수만을 사용하여 'target' 을 예측하는 그룹별 회귀모형을 적합하여 보겠습니다. 필요한 변수만 선별해서 DataFrame을 만들어보겠습니다. 



# Make a DataFrame of Y ('target')

diabetes_Y = pd.DataFrame(diabetes.target, columns = ['target'])

diabetes_Y[:5]

target
0151.0
175.0
2141.0
3206.0
4135.0

 


# Make a DataFrame of X with age, sex, bmi

diabetes_X = pd.DataFrame(diabetes.data[:, 0:3], 

                          columns = ['age', 'sex', 'bmi']) # age, sex, bmi


diabetes_X[:5]

agesexbmi
00.0380760.0506800.061696
1-0.001882-0.044642-0.051474
20.0852990.0506800.044451
3-0.089063-0.044642-0.011595
40.005383-0.044642-0.036385



diabetes_df = pd.concat([diabetes_Y, diabetes_X], axis=1)

diabetes_df[:5]

targetagesexbmi
0151.00.0380760.0506800.061696
175.0-0.001882-0.044642-0.051474
2141.00.0852990.0506800.044451
3206.0-0.089063-0.044642-0.011595
4135.00.005383-0.044642-0.036385




'sex' 변수를 가지고 'M', 'F' 를 class로 가지는 'grp'라는 범주형 변수를 만든 후에, 'sex' 변수는 삭제하겠습니다. 



diabetes_df['grp'] = np.where(diabetes_df['sex'] > 0, 'M', 'F')

diabetes_df.drop(columns=['sex'], inplace=True) 

diabetes_df[:3]

targetagebmigrp
0151.00.0380760.061696M
175.0-0.001882-0.051474F
2141.00.0852990.044451M




이제 GroupBy()의 apply()에 사용할 선형회귀모형 사용자 정의 함수(UDF)를 정의해보겠습니다. 각 그룹별 age와 bmi변수의 회귀계수를 비교하고 싶다고 했으므로 사용자 정의 함수에서 그룹별 회귀모형의 회귀계수와 절편을 결과로 반환하도록 하였습니다. 


# UDF of linear regression model

def lin_regress(data, yvar, xvars):


    # output, input variables

    Y = data[yvar]

    X = data[xvars]

    

    # Create linear regression object

    linreg = linear_model.LinearRegression()

    

    # Fit the linear regression model

    model = linreg.fit(X, Y)

    

    # Get the intercept and coefficients

    intercept = model.intercept_

    coef = model.coef_

    result = [intercept, coef]

    

    return result

 



다음으로 GroupBy()와 apply()를 사용해서 성별 그룹('M', 'F')별 선형회귀모형을 적합해보겠습니다. 



# GroupBy

grouped = diabetes_df.groupby('grp')

# Apply the UDF of linear regression model by Group

lin_reg_coef = grouped.apply(lin_regress, 'target', ['age', 'bmi'])

 



남성('M')과 여성('F') 그룹별 Y절편과 age, bmi 변수의 회귀계수 적합 결과를 조회해보겠습니다. 



lin_reg_coef

grp
F    [152.40684676047456, [23.199210147823813, 814....
M    [148.21507864445124, [291.7563226838977, 1092.... 

dtype: object 


lin_reg_coef['M']

[148.21507864445124, array([ 291.75632268, 1092.80118705])]


lin_reg_coef['F']

[152.40684676047456, array([ 23.19921015, 814.50932703])]




위의 그룹별 선형회귀무형 적합 결과로부터 우리는 아래의 모형을 얻었습니다. 

  • 남성('M') 그룹의 당뇨병 진단 target = 148.2 + 291.8*age + 1,092.8*bmi
  • 여성('F') 그룹의 당뇨병 진단 target = 152.4 + 23.2*age + 814.5*bmi

(이때 age, bmi 는 표준화한 후의 input 값임)


따라서 다른 변수가 고정되었다고 했을 때 (표준화한) bmi 값이 한 단위 증가할 때 '남성('M')' 그룹의 당뇨병 진단 target 은 1,092.8 만큼 증가하는 반면에 '여성('F') 그룹의 당뇨병 진단 target은 814.5 만큼 증가하는 것으로 나왔습니다. 


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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^^



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 그룹 별로 변수 간 상관관계 분석 (correlation with columns by groups) 하는 방법을 소개하겠습니다. 


Python pandas는 Pearson Correlation Coefficient를 구할 수 있는 corr(), corrwith() 함수를 제공합니다. 이들 함수를 groupby() 와 apply(lambda func)를 함께 사용함으로써 그룹 별 변수 간 상관계수를 구할 수 있습니다. 



[ 피어슨 상관계수 ( Pearson Correlation Coefficient) ]



먼저 예제로 사용할 'group 1'과 'group 2'의 2개의 그룹을 가진 간단한 DataFrame을 만들어보겠습니다. 




import numpy as np

import pandas as pd

from pandas import DataFrame


# making DataFrame with 4 random variables

np.random.seed(123) # for reproducibility

df= DataFrame(np.random.randn(10, 4), 

                    columns=['a', 'b', 'c', 'd'])

# setting index with 2 group, 'grp1' and 'grp2'

df['group'] = ['grp1', 'grp1', 'grp1', 'grp1', 'grp1', 

                  'grp2', 'grp2', 'grp2', 'grp2', 'grp2']


df = df.set_index('group')


df

abcd
group
grp1-1.0856310.9973450.282978-1.506295
grp1-0.5786001.651437-2.426679-0.428913
grp11.265936-0.866740-0.678886-0.094709
grp11.491390-0.638902-0.443982-0.434351
grp12.2059302.1867861.0040540.386186
grp20.7373691.490732-0.9358341.175829
grp2-1.253881-0.6377520.907105-1.428681
grp2-0.140069-0.861755-0.255619-2.798589
grp2-1.771533-0.6998770.927462-0.173636
grp20.0028460.688223-0.8795360.283627







  (1) 'd' 변수와 나머지 모든 변수 간 그룹 별 상관계수 구하기 : x.corrwith(x['d'])



# correlation with columns: corrwith()

corr_with_d = lambda x: x.corrwith(x['d'])


grouped = df.groupby('group')


grouped.apply(corr_with_d) 

abcd
group
grp10.8468220.0994170.0892051.0
grp20.3074770.832473-0.3904691.0





  (2) 'a'변수와 'd'변수 간 그룹 별 상관계수 구하기 : g['a'].corr[g['d'])



# inter-column correlation: corr()

corr_a_d = lambda g: g['a'].corr(g['d'])


grouped = df.groupby('group')


DataFrame(grouped.apply(corr_a_d))

0
group
grp10.846822
grp20.307477

 



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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^^



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 groupby() 를 사용할 때 


(1) pd.cut()으로 동일 길이로 나누어서 범주를 만든 후 GroupBy()로 그룹별 통계량 구하기

(2) pd.qcut()으로 동일 개수로 나누어서 범주를 만든 후 GroupBy()로 그룹별 통계량 구하기


를 해보겠습니다. 




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



import numpy as np

import pandas as pd

from pandas import DataFrame


np.random.seed(123)

df = DataFrame({'col_1': np.random.randint(20, size=20), 

                      'col_2': np.random.randn(20)})


df

col_1col_2
0131.730024
121.232650
22-0.823598
36-0.118201
417-0.576103
5191.695731
610-0.074394
71-1.900637
80-0.777655
9171.313462
10151.804458
119-0.965550
120-1.316480
1314-0.625785
140-0.326946
1515-0.308209
16190.827117
17141.070781
184-3.055577
1901.005932

 




  (1) pd.cut : 동일 길이로 나누어서 범주 만들기(equal-length buckets categorization)


'col_1' 칼럼에 대해서 4개의 동일한 길이로 범주를 만들어보겠습니다. 

카테고리의 구간이 [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]] 로서 4개의 각 구간의 길이가 동일함을 알 수 있습니다.



factor_col_1 = pd.cut(df.col_1, 4)

factor_col_1

0 (9.5, 14.25]

1     (-0.019, 4.75]
2     (-0.019, 4.75]
3        (4.75, 9.5]
4      (14.25, 19.0]
5      (14.25, 19.0]
6       (9.5, 14.25]
7     (-0.019, 4.75]
8     (-0.019, 4.75]
9      (14.25, 19.0]
10     (14.25, 19.0]
11       (4.75, 9.5]
12    (-0.019, 4.75]
13      (9.5, 14.25]
14    (-0.019, 4.75]
15     (14.25, 19.0]
16     (14.25, 19.0]
17      (9.5, 14.25]
18    (-0.019, 4.75]
19    (-0.019, 4.75]
Name: col_1, dtype: category
Categories (4, interval[float64]): [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]]



이제 'factor_col_1'이라는 'col_1' 칼럼에 대한 4개 구간의 범주를 GroupBy() 에 넣어서 각 범주의 그룹별로 agg() 함수로 개수(count), 평균(mean), 표준편차(std), 최소값(min), 최대값(max) 값을 계산해보겠습니다. 



grouped_col_1 = df.col_1.groupby(factor_col_1)

grouped_col_1.agg(['count', 'mean', 'std', 'min', 'max'])

countmeanstdminmax
col_1
(-0.019, 4.75]81.1251.45773804
(4.75, 9.5]27.5002.12132069
(9.5, 14.25]412.7501.8929691014
(14.25, 19.0]617.0001.7888541519



위와 동일한 결과를 아래 처럼 통계집계를 하는 사용자정의함수와 apply() 를 사용해서 구할 수도 있습니다. 


 

def summary_func(group):

    return {'count': group.count()

             'mean': group.mean()

             'std': group.std()

             'min': group.min()

             'max': group.max()}


grouped_col_1.apply(summary_func)

col_1                
(-0.019, 4.75]  count     8.000000
                max       4.000000
                mean      1.125000
                min       0.000000
                std       1.457738
(4.75, 9.5]     count     2.000000
                max       9.000000
                mean      7.500000
                min       6.000000
                std       2.121320
(9.5, 14.25]    count     4.000000
                max      14.000000
                mean     12.750000
                min      10.000000
                std       1.892969
(14.25, 19.0]   count     6.000000
                max      19.000000
                mean     17.000000
                min      15.000000
                std       1.788854
Name: col_1, dtype: float64




위의 결과를 좀더 보기에 좋도록 unstack()를 사용해서 길게(long) 제시된 결과를 옆으로 넓게(wide) 표형식으로 만들어보겠습니다. 



grouped_col_1.apply(summary_func).unstack()

countmaxmeanminstd
col_1
(-0.019, 4.75]8.04.01.1250.01.457738
(4.75, 9.5]2.09.07.5006.02.121320
(9.5, 14.25]4.014.012.75010.01.892969
(14.25, 19.0]6.019.017.00015.01.788854

 



위의 결과의 'count' 개수 부분을 보면 각 범주 구간  [(-0.019, 4.75] < (4.75, 9.5] < (9.5, 14.25] < (14.25, 19.0]] 그룹 별로 개수가 8개, 2개, 4개, 6개로서 각각 다릅니다. 이는 랜덤 숫자에 대해서 구간별 길이를 동일하게 했기 때문에 구간 그룹별 개수가 다르게 된 것입니다.


그러면, 다음으로 구간별 '동일한 개수(equal-size)'로 범주 바구니(bucket categorization)를 만들어보겠습니다.




  (2) pd.qcut() : 동일 개수로 나누어서 범주 만들기 (equal-size buckets categorization)


pd.qcut() 함수를 사용하여 'col_2'에 대해서 각 범주 바구니별로 동일하게 4개의 개수를 가지도록 범주를 만들어보겠습니다. 이때 labels=False 로 설정하여 label이 0, 1, 2, 3 이런 식으로 0부터 순차적으로 1씩 증가하게 하였습니다. 



bucket_qcut_col_2 = pd.qcut(df.col_2, 4, labels=False)

bucket_qcut_col_2

0     3
1     3
2     0
3     2
4     1
5     3
6     2
7     0
8     1
9     3
10    3
11    0
12    0
13    1
14    1
15    1
16    2
17    2
18    0
19    2
Name: col_2, dtype: int64




아래처럼 labels=np.arange(4, 0, -1)로 직접 지정을 해주면 label이 4, 3, 2, 1 이런식으로 4부터 1씩 줄어드는 순서로 할당이 됩니다. 위의 label 과 정 반대로 할당이 되었습니다. 



bucket_qcut_label_col_2 = pd.qcut(df.col_2, 4, labels=np.arange(4, 0, -1))

bucket_qcut_label_col_2

0     1
1     1
2     4
3     2
4     3
5     1
6     2
7     4
8     3
9     1
10    1
11    4
12    4
13    3
14    3
15    3
16    2
17    2
18    4
19    2
Name: col_2, dtype: category
Categories (4, int64): [4 < 3 < 2 < 1]




그럼 [4 < 3 < 2 < 1] 순서로 동일 개수로 나눈 4개의 그룹별 통계량을 계산해보겠습니다. 



grouped = df.col_2.groupby(bucket_qcut_label_col_2)

grouped.apply(summary_func).unstack()

countmaxmeanminstd
col_2
45.0-0.823598-1.612369-3.0555770.907474
35.0-0.308209-0.522940-0.7776550.201746
25.01.0707810.542247-0.1182010.589903
15.01.8044581.5552651.2326500.262163



'count' 개수가 4개의 각 그룹별로 모두 '5'로서 동일한 것을 알 수 있습니다. 


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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. :-)



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Pandas DataFrame에서 groupby() 를 사용해 그룹 단위로 요약 통계량(group-level statistics aggregation)을 집계하여 원래의 DataFrame에 transform() 함수를 사용하여 통계량 칼럼을 추가하는 방법을 소개하겠습니다. 




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



 import numpy as np

 import pandas as pd

 from pandas import DataFrame


 df = DataFrame({'group_1': ['a', 'a', 'a', 'a', 'a',  

                                      'b', 'b', 'b', 'b', 'b',], 

                       'group_2': ['c', 'c', 'c', 'd', 'd', 

                                     'e', 'e', 'e', 'f', 'f'], 

                       'col': [1, 2, np.NaN, 4, np.NaN, 

                              6, 7, np.NaN, 9, 10]})

 

 df

colgroup_1group_2
01.0ac
12.0ac
2NaNac
34.0ad
4NaNad
56.0be
67.0be
7NaNbe
89.0bf
910.0bf




위의 DataFrame에 대해서 


(1)  GroupBy() 로 'group_1'과 'group_2' 칼럼을 모두 적용한 그룹을 만들어서
     [그룹 ('a', 'c'), 그룹 ('a', 'd'), 그룹 ('b'e, 'e'), 그룹 ('b', 'f')]


(2) 그룹별로 transform() 함수를 사용하여 NaN이 아닌 원소의 '개수(count)', '합(sum)', '최대값(max)'을 계산하여 


(3) df의 DataFrame에 새로운 칼럼을 추가해보겠습니다. 



  (a) 그룹 별 NaN이 아닌 원소의 개수 구하여 데이터프레임에 새로운 칼럼 추가하기



# 'count' grouped by (['group_1', 'group_2'])

 df['count_col'] = df.groupby(['group_1', 'group_2']).col.transform('count')

 df

col

group_1group_2

count_col

01.0ac2.0
12.0ac2.0
2NaNac2.0
34.0ad1.0
4NaNad1.0
56.0be2.0
67.0be2.0
7NaNbe2.0
89.0bf2.0
910.0bf2.0

 




  (b) 그룹 별 NaN이 아닌 원소의 합을 구하여 데이터프레임에 새로운 칼럼 추가하기



 # 'sum' grouped by (['group_1', 'group_2'])

 df['sum_col'] = df.groupby(['group_1', 'group_2']).col.transform('sum')

 df

colgroup_1group_2count_colsum_col
01.0ac2.03.0
12.0ac2.03.0
2NaNac2.03.0
34.0ad1.04.0
4NaNad1.04.0
56.0be2.013.0
67.0be2.013.0
7NaNbe2.013.0
89.0bf2.019.0
910.0bf2.019.0





  (c) 그룹 별 NaN이 아닌 원소 중 최대값을 구하여 데이터프레임에 새로운 칼럼 추가하기



 # 'max' grouped by (['group_1', 'group_2'])

 df['max_col'] = df.groupby(['group_1', 'group_2']).col.transform('max')

 df

colgroup_1group_2count_colsum_colmax_col
01.0ac2.03.02.0
12.0ac2.03.02.0
2NaNac2.03.02.0
34.0ad1.04.04.0
4NaNad1.04.04.0
56.0be2.013.07.0
67.0be2.013.07.0
7NaNbe2.013.07.0
89.0bf2.019.010.0
910.0bf2.019.010.0




이번 포스팅에서는 groupby() 연산자와 transform('statistics function') 함수를 사용하여 그룹별로 통계량을 계산하여 기존의 DataFrame에 새로운 그룹별 집계된 통계량을 새로운 변수로 추가하는 방법에 대해서 알아보았습니다. 


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


이번 포스팅이 도움이 되었다면 아래의 '공감~'를 꾹 눌러주세요. ^^



728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python pandas의 GroupBy operation을 이용해서 그룹 별 가중평균(Group weighted average)을 구하는 방법을 소개하겠습니다. 


앞서 GroupBy 연산자의 원리에서 소개드렸던 것처럼, Split => Apply => Combine 의 절차를 거치면서 각 그룹별 GroupBy 연산을 실행하게 됩니다. 


[GroupBy 를 이용한 그룹별 가중 평균 구하기 절차]




예제로 사용할 'a'와 'b'의 두 개 그룹별로 'value 값'과 'weight 가중치'를 가지고 있는 간단한 데이터 프레임을 만들어보겠습니다. 



import pandas as pd

import numpy as np


df = pd.DataFrame({'grp_col' : ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b'], 

                   'val' : np.arange(10)+1,                   

                   'weight' : [0.0, 0.1, 0.2, 0.3, 0.4, 0.0, 0.1, 0.2, 0.3, 0.4]})


df

grp_colvalweight
0a10.0
1a20.1
2a30.2
3a40.3
4a50.4
5b60.0
6b70.1
7b80.2
8b90.3
9b100.4



  (1) GroupBy 를 활용하여 그룹 별 가중 평균 구하기


이제 (1) GroupBy 객체를 만들고, (2) 가중평균을 구하는 lambda 함수를 정의한 후에, (3) grouped.apply(function) 로 그룹별 가중평균을 구해보겠습니다. 


 

# group weighted average by category

grouped = df.groupby('grp_col')

weighted_avg_func = lambda g:np.average(g['val'], weights=g['weight'])

grouped.apply(weighted_avg_func)

grp_col
a    4.0
b    9.0
dtype: float64




 (2) 수작업으로 그룹 별 가중 평균 구하기 (Split -> Apply -> Combine) 


위에서 처럼 GroupBy 를 사용하지 않는 다면 아래에 소개한 것처럼 각 그룹별로 하나씩 Split -> Apply 하고 마지막에 Combine 을 해주는 단순 반복작업을 그룹의 개수만큼 해주어야 합니다. 


그룹의 개수가 적으면 할만 한데요, 그룹의 개수가 많아지면 수고롭기도 하고, 자칫 실수도 유발할 수 있으니 위의 (1)번 GroupBy 연산자를 사용하는 방법이 좀더 추천할만 하다고 하겠습니다. 비교를 위해서 소개합니다. 


  • (a) Split


# split

df_a = df[df['grp_col']=='a']

df_b = df[df['grp_col']=='b']

 


 df_a

grp_colvalweight
0a10.0
1a20.1
2a30.2
3a40.3
4a50.4

 df_b

grp_colvalweight
5b60.0
6b70.1
7b80.2
8b90.3
9b100.4



  • (b) Apply


# apply

weighted_avg_a = sum((df_a['val']*df_a['weight']))/sum(df_a['weight'])

weighted_avg_b = sum((df_b['val']*df_b['weight']))/sum(df_b['weight'])



 weighted_avg_a

4.0

 weighted_avg_b

9.0



  • (c) Combine


# combine

weighted_avg_ab = pd.DataFrame({'grp_col': ['a', 'b'], 

                               'weighted_average': [weighted_avg_a, weighted_avg_b]})


weighted_avg_ab

grp_colweighted_average
0a4.0
1b

9.0




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



728x90
반응형
Posted by Rfriend
,