Graph2) 영업사원의 영업 관련 그래프

1. 시각화 목적:

1) 영업사원의 프로모션이 얼마큼 효과가 있었는지 확인하기 위한 그래프

2) 영업사원이 제시한 상품과 프리젠테이션 만족도 별 상품 신청률을 확인하여 향후 영업과 마케팅에 활용이 가능

2. 시각화 과정 소개:

1) 영업사원과 관련된 feature들만 따로 모아 df 분리
2) 추천 상품별 신청/미신청 건수를 그래프로 그림
3) 추천 제품에 대한 소비자들의 영업 만족도를 구분하여 신청률을 그래프로 표현
4) 평균선을 추가하여 전체 평균과 비교 가능하도록 표현

3. 피드백:

1) 색깔이랑 스타일 등이 조금 더 심미적인 그래프가 되도록 그릴 필요가 있음

Graph2-1) 추천 상품별 신청/미신청(ProdTaken) 건수

 
# 영업사원과 관련된 컬럼만 가져와 df 분리
pitch_list = ['DurationOfPitch', 'NumberOfFollowups', 'ProductPitched', 'PitchSatisfactionScore', 'ProdTaken']
pitch_df = df[pitch_list]
pitch_df.head(3)
 
plt.figure(figsize=(8,6))
sns.countplot(x="ProductPitched", hue="ProdTaken", data=pitch_df)
plt.title('<추천 상품별 신청/미신청 건수>')
plt.show()

Graph2-2) 상품의 프리젠테이션 만족도별 신청률

pd.DataFrame(pitch_df.groupby(['ProductPitched', 'PitchSatisfactionScore'])['ProdTaken'].mean().unstack())
# pd.DataFrame(pitch_df.pivot_table('ProdTaken', index = 'ProductPitched', columns = 'PitchSatisfactionScore'))
 

#평균선을 그리기 위한 평균값 구하기
mean_taken = np.round(pitch_df['ProdTaken'].mean(), 3)

plt.figure(figsize=(12,6))
sns.catplot(x = 'ProductPitched', hue = 'PitchSatisfactionScore', legend=False,
            y = 'ProdTaken', kind= 'bar', data = pitch_df, height = 7, aspect=2)

plt.axhline(mean_taken, label='평균', linestyle = '--', linewidth = 3, color = 'r') ## 평균값을 y좌표로 하는 수평선 생성
plt.text(4, mean_taken + 0.01, f'평균값 : {mean_taken}', fontsize=25, fontweight = 'semibold') ## 평균에 대한 텍스트 출력

plt.legend(title='PitchSatisfactionScore', loc='upper right', fontsize = 15)
plt.xlabel('추천 상품', fontsize=16);
plt.ylabel('신청률', fontsize=16);
plt.title('<추천 상품 & 만족도 별 신청률>', fontsize=20)
plt.tick_params(axis='both', which='major', labelsize=14)
plt.show()

Graph3) 연령대별 패키지 여행 신청(ProdTaken) 비율

1. 시각화 목적:

1) 각 연령대별로 패키지 여행을 얼마나 신청했는지 그 비율을 그래프로 표현

2) 신청율이 떨어지는 연령대에 대해 추가적인 마케팅과 영업을 통해 성공율을 높일 수 있을 것으로 기대

2. 시각화 과정 소개:

1) '연령대(Ageband)'와 '신청 여부('ProdTaken)'간 cross_tab_prop 테이블 생성

2) 테이블을 horizontal bar plot으로 전체 100% 기준 비율 그래프로 변환
3) 그래프의 각 비율에 몇 퍼센트인지 텍스트를 첨가

3. 피드백:

1) 'ProdTaken'에서 0은 미신청이고 1이 신청인데 그래프를 보게 되면 0에 색깔이 부여되어 미신청이 마치 신청인 것처럼 보이게 됨

2) 0과 1의 순서를 바꾸어 줄 필요가 있음

cross_tab = pd.crosstab(index=df['Ageband'],
                        columns=df['ProdTaken'],
                        normalize= False)

cross_tab_prop = pd.crosstab(index=df['Ageband'],
                             columns=df['ProdTaken'],
                             normalize= 'index')
cross_tab_prop
 
cross_tab_prop.plot(kind='barh', 
                        stacked=True, 
                        colormap='Pastel1', 
                        figsize=(10, 6))

plt.legend(loc="lower right", ncol=3)
plt.ylabel("연령", fontsize= 'large')
plt.xlabel("신청 비율", fontsize= 'large')
plt.title('<연령대별 신청율 차이>', fontsize= 'xx-large', fontweight= 'bold')


for n, x in enumerate([*cross_tab.index.values]):
    for (proportion, count, y_loc) in zip(cross_tab_prop.loc[x],
                                          cross_tab.loc[x],
                                          cross_tab_prop.loc[x].cumsum()):
                
        plt.text(x=(y_loc - proportion) + (proportion * 1/4),
                 y=n - 0.11,
                 s=f'{count} ({np.round(proportion * 100, 1)}%)', 
                 color="Black",
                 fontsize=10,
                 fontweight="normal")

plt.show()

ㄴㅇㄹㄴㅇㄹ

info) 정규세션 2주차 Tourism 데이터 시각화

Tourism 데이터를 활용하여 시각화 그래프 그리기


1. 초기 환경설정 

from IPython.core.display import display, HTML
display(HTML("<style>.container {width:80% !important;}</style>"))
%matplotlib inline

import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

mpl.rcParams['figure.figsize'] = (12,8)  #시각화 figure default 설정
mpl.rcParams['font.family'] = 'NanumGothic' #폰트 디폴트 설정
mpl.rcParams['font.size'] = 10    #폰트 사이즈 디폴트 설정
plt.rcParams['axes.unicode_minus'] = False
%config InlineBackend.figure_format='retina' # 그래프 글씨 뚜렷

2. Tourism 데이터 로드

import pandas as pd
import numpy as np

df = pd.read_csv('./tourism.csv')
df.head(3)

공간 상 일부 컬럼 생략되었습니다


3. 데이터 전처리

3.1 Gender의 'Fe Male' 오기 수정

# 오타가 발생한 'Fe Male'을 'Female'로 수정
df['Gender'].replace('Fe Male', 'Female', inplace=True)

# 수정된 결과 확인
df['Gender'].unique()

3.2 'Age' 결측치 처리

# 성별 나이 중앙값
male_median_age = df.loc[df['Gender'] == 'Male', 'Age'].median() #남자의 나이 중앙값
female_median_age = df.loc[df['Gender'] == 'Female', 'Age'].median() #여자의 나이 중앙값

print('남성 나이 중앙값: ', male_median_age, '여성 나이 중앙값: ', female_median_age)
# Age의 결측치를 중앙값으로 대치
median_age = df['Age'].median()
df['Age'].fillna(median_age, inplace = True)

# Age 값들 확인
df['Age'].unique()
 
array([28., 34., 45., 29., 42., 32., 43., 36., 35., 31., 49., 52., 33.,
       22., 50., 23., 41., 37., 40., 56., 54., 39., 20., 46., 27., 38.,
       25., 26., 24., 30., 21., 51., 47., 55., 44., 53., 48., 18., 57.,
       60., 59., 19., 58., 61.])

3.3 연령대(Ageband) 파생변수 생성

# 연령대를 반환하는 ageband() 정의
def ageband(x):
    if x < 10:
        return '10세 이하'
    elif x < 20:
        return '10대'
    elif x < 30:
        return '20대'    
    elif x < 40:
        return '30대'
    elif x < 50:
        return '40대'
    elif x < 60:
        return '50대'
    elif x < 70:
        return '70대'
    else:
        return '80대 이상'
df['Ageband'] = df['Age'].apply(lambda x: ageband(x)) 

4. 그래프 그리기

Graph1) EDA 그래프

1. 시각화 목적:

1) 전반적인 데이터의 상황을 한눈에 확인하기 위한 목적
2) 개괄적으로 데이터를 살핀 후 필요에 따라 drill-down하여 세부적으로 확인 가능

2. 시각화 과정 소개:

1) 범주형 변수 연속형 변수를 분리하여 따로 그래프를 그림
2) subplots를 통해 한번에 여러 그래프를 확인할 수 있도록 함
3) 색상은 구매선택과 미선택 사이에 극명한 차이를 둘 수 있도록 대조되는 컬러 사용
4) 연속형 변수에 대한 그래프의 경우, kde(커널 밀도 추청) 그래프를 추가하여 히스토그램에 대한 확률밀도함수를 추정하여 정밀 표현

3. 피드백:

1) 색상 지정에서 hue의 각 변수에 대해 새상을 따로 지정할 수 있는지 확인이 필

2) 가령, 0(미선택)에 대해 부정을 의미하는 빨간색, 1(선택)에 대해 긍정을 의미하는 파란색 부여가 가능한지

Graph1-1) 카테고리형 변수별 상품 신청률(ProdTaken)
 
#카테고리 변수만 따로 리스트로 분리하고 개수 출력
categorical_list = ['TypeofContact', 'CityTier', 'Occupation', 'Gender', 'NumberOfPersonVisiting', 'PreferredPropertyStar', 'MaritalStatus',
       'NumberOfTrips', 'OwnCar','NumberOfChildrenVisiting', 'Designation']
print('카테고리 변수 개수: ', len(categorical_list))

#subplot으로 11개의 변수를 한번에 표시
fig, axes = plt.subplots(3, 4, figsize = (24, 24))

for idx, cat in enumerate(categorical_list):
    row = idx // 4
    col = idx % 4
    sns.countplot(x = cat, hue = "ProdTaken", palette = 'deep', data=df, ax=axes[row][col])
    
plt.tight_layout()
plt.show()

Graph1-2) 연속형 변수별 신청률(ProdTaken)

#연속형 변수만 따로 리스트로 분리하고 개수 출력
continuous_list = ['Age', 'DurationOfPitch', 'MonthlyIncome']
print('연속형 변수 개수: ', len(continuous_list))

#subplot으로 11개의 변수를 한번에 표시
fig, axes = plt.subplots(1, 3, figsize = (12, 6), squeeze=False)

for idx, con in enumerate(continuous_list):
    row = idx // 4
    col = idx % 4
    sns.histplot(data=df, x=con, hue="ProdTaken", palette = 'Set1', kde= True, multiple='stack', ax=axes[row][col])
    
plt.tight_layout()
plt.show()

 

 

info) 서브세션 3주차


[문제 1] While문을 활용하여 달러, 원화, 유로의 환산표를 만드는 프로그램을 작성하시오.

참조) • 1$당 환율 : 1443원(원화), 1.02€(유로)

from IPython.display import Image
Image("screenshot.png", width = 300)

[풀이]

import numpy as np

print("-" * 30)
print("달러($)  원화(원)  유로(€)")
print("-" * 30)
d = 10
while d <= 100:
    print("{}       {}        {}".format(d, d*1440, np.round((d*1.02), 1)))
    d = d + 10
print("-" * 30)

[문제2] 세 정수를 입력받아 중앙값을 리턴하는 함수를 작성하시오.

median3(a, b, c)

[풀이1]

def median3(a, b, c):
    num_list = []
    num_list.append(a)
    num_list.append(b)
    num_list.append(c)
    num_list.sort()
    result = num_list[1]
    return result
median3(245, 23, 198)

[풀이2]

def median(a, b, c):
    if a >= b:
        if b >= c:
            return b
        elif a <= c:
            return a
        else:
            return c
    elif a >= c:
        return a
    elif b <= c:
        return b
    else:
        return c
median(9, 7, 8)

[문제3] 리스트내에서 중앙값을 리턴하는 함수를 작성하시오.

median_list(num_list)

< 검증 데이터 >

even_len = [8, 46, 9, 83, 20, 37, 3, 43, 19, 96]
odd_len = [49, 5, 67, 39, 61, 92, 71, 57, 58, 2, 89]

median_list(even_len) 결과 ⇒ 28.5
medain_list(odd_len) 결과 ⇒ 58

[풀이]

def median_list(num_list):
    num_list.sort()
    if len(num_list) % 2 == 0:
        result = (num_list[len(num_list) // 2] + num_list[(len(num_list) // 2) - 1]) / 2
    else:
        result = num_list[len(num_list) // 2]
    return result
even_len = [8, 46, 9, 83, 20, 37, 3, 43, 19, 96]
odd_len = [49, 5, 67, 39, 61, 92, 71, 57, 58, 2, 89]

print("np.median() 사용: ", np.median(even_len))
print("median_list() 사용: ", median_list(even_len))
print("")
print("np.median() 사용: ", np.median(odd_len))
print("median_list() 사용: ", median_list(odd_len))

[문제4] 문자열의 위치를 바꾸는 함수를 작성하시오. 조건은 다음과 같습니다.

shiftStringLeft(string)

return 값 : 문자열에 속한 글자의 위치가 다음처럼 변경된 새로운 문자열 하나

  • 입력 파라메타로 받은 문자열의 맨 왼쪽의 글자는 새로운 문자열에서 맨 오른쪽으로 이동함
  • 입력 파라메타로 받은 문자열 중 맨 왼쪽의 글자가 아닌 경우는, 왼쪽으로 한칸씩 이동함
  • 예를 들어, 입력파라메타로 ‘ABCDEF’를 받으면, return 값은 ‘BCDEFA’임
def shiftStringLeft(string):
    new_string = ''
    for i in range(1, len(string)):
        new_string += string[i]
    new_string += string[0]
    return new_string        
string = 'ABCDE'
shiftStringLeft(string)
 

[문제5] 리스트 내에서 최대값을 반환하는 함수를 작성하시오.

num_max(num_list)

단, max(), np.max() 등의 함수를 사용하는 것이 아니며, 제어문으로 작성해야 합니다.

[풀이 1]

def num_max(num_list):
    max = num_list[0]
    for num in num_list:
        if max < num:
            max = num
    return max
num_list = [2,3,6,107,24,53,44,3,7,8,1]
num_max(num_list)
 
 

[풀이 2]

def num_max1(num_list):
    max = num_list[0]
    idx = 1
    while idx <= (len(num_list)-1):
        if max >= num_list[idx]:
            max
        else:
            max = num_list[idx]
        idx += 1
    return max
num_list = [2,3,6,107,24,53,44,3,70,8,1]
num_max1(num_list)
 

[문제6] 리스트의 길이를 반환하는 함수를 작성하시오.

length(num_list)

단, len() 등의 함수를 사용하는 것이 아니며, 제어문으로 작성해야 합니다.

def length(num_list):
    count = 0
    for i in num_list:
        count += 1
    return count
num_list = [2,3,6,7,24,543,2342,3,7,8,1]
print("len() 사용: ", len(num_list))
print("length() 사용: ", length(num_list))

'BACS > 서브세션' 카테고리의 다른 글

[BACS] 서브세션 2주차  (0) 2022.10.01
[BACS] 서브세션 1주차  (0) 2022.09.27

info) 정규세션 2주차 Titanic 시각화 실습

Titanic 데이터를 활용하여 시각화 그래프 그리기


Graph2) Pclass별 투숙객들의 연령 분포 BoxPlot / 가족 구성원에 따른 생존율 차이 HeatMap

1-1. 시각화 목적

1) 각 객실등급(Pclass)별 투숙객들의 정보를 확인하기 위한 목적에서 그래프를 그려보았습니다. 여러 정보들 중에서도 연령분포를 확인해보았습니다.

2) 가족관계가 생존율에 영향을 주었는지 살펴보기 위한 목적에서 그래프를 그려보았습니다. 가족에 대한 변수 중 'SibSp(형제/자매)'와 'Parch(부모/자녀)'를 사용하여 구성원에 따른 생존율 차이를 시각화하고자 하였습니다.

1-2. 시각화 과정 소개

1) 연속형 데이터에 대한 최대, 최소, 중앙값, 이상치 등을 한 눈에 확인할 수 있다는 점과 범주형 변수와 연속형 변수 사이의 비교가 용이하다는 점에서 boxplot을 사용하였습니다. seaborn의 catplot함수에서 'box' 파라미터를 지정하여 그래프를 만들었습니다. 

2) 'SibSp'와 'Parch'는 둘 다 카테고리 변수로 같은 단위("명")를 공유하고 있습니다. 2개의 변수 사이의 관계를 살펴보기 위해 crosstab(교차표)를 그려볼 수 있는데 이를 시각화하면 어떨까 하는 생각이 들었습니다. 언젠가 heatmap을 통해 시계열 데이터(월, 시간) 간의 관계를 표현한 heatmap 그래프를 본 적이 있습니다. crosstab의 형태가 heatmap과 잘 어울린다고 판단되어 crosstab을 heatmap 그래프로 변환하였습니다. (실제로 상관계수(Correlation coefficient)와 오분류표(confusion matrix)와 같은 범주형 변수 사이의 관계를 나타내는 표를 heatmap으로 시각화하는 작업이 많이 알려져 있습니다.) 

1-3. 그래프 그리기

Graph2-1) Pclass별 투숙객들의 연령 분포 BoxPlot

sns.catplot(data=df, x="Pclass", y="Age", kind="box", height = 4, aspect = 1);

분석 : 그래프를 그려본 결과 3등급에서 1등급으로 갈 수록 평균연령을 비롯한 연령 분포가 점차 높아지는 것을 확인할 수 있습니다. 1등급의 경우 정규분포에 가까운 형태라고 할 수 있고 나머지 등급의 경우에는 고령층에 이상치가 발견된다는 점에서 히스토그램으로 변환했을 때 right-skewed된 형태가 될 것으로 짐작됩니다. 

Graph2-2) 가족 구성원에 따른 생존율 차이 HeatMap

Parch와 SibSp를 각각 index와 column으로 하는 pivot table을 그려보았습니다. 앞서 설명에서는 crosstab이라고 말씀 드렸는데 지금 보니 pivot table을 그렸던 것으로 보입니다. 사실 crosstab이 pivot table의 일종이기 때문에 그다지 큰 차이는 없습니다. 그리고 여기서는 groupby 집계 후에 pivot()함수를 사용하였는데 이를 한번에 pivot_table()로 처리(aggfunc = 'mean')할 수도 있습니다. 

# 그래프를 그리기 위한 피봇테이블 생성
family_df = df[["PassengerId","SibSp", "Parch", "Survived"]]
family_df = pd.DataFrame((family_df.groupby(['SibSp','Parch'])['Survived'].mean() * 100).round(1))
family_df.reset_index(inplace = True)
pivot_df = family_df.pivot(index = 'SibSp', columns = 'Parch', values = 'Survived')
print(pivot_df)

위에서 만들어진 pivot table을 heatmap으로 변환해보도록 하겠습니다. 

# heatmap 그래프로 생존율의 차이 확인
plt.figure(figsize = (8, 6))
ax = sns.heatmap(pivot_df, annot = True, linewidths = .5, cmap = 'Reds', fmt = 'g')
plt.xlabel('부모 또는 자녀의 수')
plt.ylabel('형제 자매 수')
plt.title('<가족 관계에 따른 생존율 차이>', fontweight = 'semibold')
plt.show()

분석 : null 값이 많았던 터라 heatmap의 경우도 값이 빈 경우가 많고 때문에 적은 값(가족 인원 수)들에 분포가 몰린 것을 볼 수 있습니다. 데이터 자체가 heatmap으로 표현하기에는 부족했던 것 같습니다. 또한, 데이터 분포를 보더라도 그다지 패턴이나 군집화(cluster) 경향을 확인할 수 없습니다. 물론 더 정확한 분석을 위해서는 다른 분석 기법을 통해 확인해보아야겠지만 시각화 결과만으로는 그다지 뚜렷한 관계를 파악할 수는 없습니다. 그냥 재미로 보고 넘어가는 것이 바람직할 것 같습니다. 

info) 정규세션 2주차 Titanic 시각화 실습

Titanic 데이터를 활용하여 시각화 그래프 그리기


1. 라이브러리 import 및 환경 설정

#주피터 노트북 환경 설정
from IPython.core.display import display, HTML
display(HTML("<style>.container {width:80% !important;}</style>")) #주피터 노트북 셀 확장
%matplotlib inline #그래프를 셀에서 그리기

#필요한 라이브러리 import
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

#맷플로립 그래프 초기 환경 설정
mpl.rcParams['figure.figsize'] = (12,8)  #figure size default 설정
mpl.rcParams['font.family'] = 'NanumGothic' #한글 폰트 default 설정
mpl.rcParams['font.size'] = 15    #폰트 사이즈 default 설정
plt.rcParams['axes.unicode_minus'] = False #한글 마이너스 기호 깨짐 방지
%config InlineBackend.figure_format='retina' # 그래프 글씨를 뚜렷하게 설정

2. Titanic 데이터 불러오기

df = pd.read_csv('./Titanic.csv')
df.head(3)

3. 시각화 작업

Graph1) 생존자 연령 분포에 대한 Violin Plot(바이올린 플롯)

1-1. 시각화 목적

1) 타이타닉 생존자에 대한 대략적인 분포를 파악하기 위한 목적에서 그래프를 그려보았습니다.

2) 구체적으로 생존에 영향을 끼친 2개의 feature값 'Sex(성별)'과 'Pclass(객실등급)' 각각에 대한 생존/사망자들의 연령 분포를 확인하고자 그래프를 그렸습니다. 

1-2. 시각화 과정 소개

1) 분포를 확인할 수 있는 방법으로 hist plot, box plot 등 여러 방법이 있지만 hist plot은 두 히스토그램이 겹치게 그려진다는 단점이 있고 box plot은 그래프에 대한 지식이 없이는 직관적으로 해석하기 어렵다는 단점이 있습니다. 따라서, 이 둘을 보완할 수 있는 Violin plot을 선택하였습니다.

1-3. 그래프 그리기

#subplot으로 2개의 그래프를 한번에 그릴 수 있도록 설정
fig,ax=plt.subplots(1, 2, figsize=(18,8))

#첫 번째 그래프
sns.violinplot(data=df, x="Pclass", y="Age", hue="Survived", split = True, ax = ax[0])
ax[0].set_title('Pclass별 Survived의 Age 분포', fontsize = 20, fontweight ='bold')
ax[0].set_yticks(range(0,100,10)) #연령의 bin을 10으로 설정

#두 번째 그래프
sns.violinplot(data=df, x="Sex", y="Age", hue="Survived", split = True, ax = ax[1])
ax[1].set_title('Sex별 Survived의 Age 분포',  fontsize = 20, fontweight ='bold')
ax[1].set_yticks(range(0,100,10))  #연령의 bin을 10으로 설정

plt.tight_layout()
plt.show()


1-4.  자체 피드백

1) 위 그래프를 통해서는 생존에 대한 심층적인 분석이 불가능하다고 보입니다. 우리의 목적은 생존에 어떤 요인들이 영향을 미쳤고 어떤 사람들이 생존에 유리했는지 파악하는 것인데 상기의 그래프를 통해서는 이를 분석하기 어렵다고 생각됩니다.

2) 가령, 연령별 생존 여부를 확인하기 위해서는 생존자 분포가 아닌 생존율을 살펴볼 필요가 있습니다. 각 연령별로 인원수가 같지 않기 때문에 절대적인 수를 가지고 비교하게 되면 잘못된 해석이 될 수 있습니다.

3) 때문에 단순 count를 기반으로 하는 그래프가 아닌 생존율을 비교하는 그래프로 다시 바꾸도록 하겠습니다. 아래 그래프들은 상단의 그래프 각각을 분석 목적에 맞춰 새로 변환한 것입니다.


1-5. 피드백을 반영한 그래프

1) 연령대별 생존율

def ageband(x):
    if x < 10:
        return '0-9세'
    elif x < 20:
        return '10대'
    elif x < 30:
        return '20대'    
    elif x < 40:
        return '30대'
    elif x < 50:
        return '40대'
    elif x < 60:
        return '50대'
    elif x < 70:
        return '60대'
    elif x < 80:
        return '70대'
    else:
        return '80대 이상'
#ageband()함수를 통해 연령대를 나타내는 새로운 컬럼 Ageband 생성
df['Ageband'] = df['Age'].apply(lambda x: ageband(x)) 
df.sort_values('Ageband', inplace = True)

#연령대별 생존율에 대한 barplot
sns.catplot(data=df, x="Ageband", y="Survived", kind="bar", height=4, aspect=3)

#생존율 평균값을 그리기
mean_survived = np.round(df['Survived'].mean(), 3) #전체 승객에 대한 생존율 평균
plt.axhline(mean_survived, label='평균', linestyle = '--', linewidth = 3, color = 'r') #평균값을 y좌표로 하는 수평선 생성
plt.text(7, mean_survived + 0.05, f'평균값 : {mean_survived}', fontsize=15, fontweight = 'semibold') #평균에 대한 텍스트 출력

plt.xlabel('연령대')
plt.ylabel('생존율')
plt.ylim((0, 1))
plt.title('연령별 생존율')
plt.show()

분석 : 10대 이하, 80대 이상을 제외하고 대부분의 연령대는 평균 생존율에 근사해 있습니다. 70대의 경우에는 생존자가 없으며, 10대 이하의 생존율은 평균 이상이고 80대 이상의 생존율은 평균 이하입니다. 어린이에 비해서 노인들이 생존에 있어 취약한 면이 있었다고 보입니다.


2) 객실 등급별 남여 생존율 차이

#Pclass와 Sex별 생존율을 나타내는 df
pd.DataFrame(df.groupby(['Pclass', 'Sex'])['Survived'].mean())
fig = sns.catplot(data=df, x="Sex", y="Survived", col="Pclass", kind="bar", height=4, aspect=.6,)
fig.set_axis_labels("", "생존율")
fig.set_xticklabels(["남성", "여성"])
fig.set_titles("{col_var} {col_name}")
fig.set(ylim=(0, 1))
plt.show()

분석 : 전반적으로 남성보다 여성의 생존율이 월등히 높습니다. 남성의 경우에는 1등급 객실의 생존율이 다른 객실보다 높은 편이며 남녀 상관없이 3등급 객실의 생존율이 가장 낮습니다. 특히, 여성에 있어 다른 1,2등급 객실은 생존율이 90% 이상으로 높은 편이지만 3등급 여성 승객들은 50%의 매우 낮은 확률을 보이고 있습니다. 이 점에 대해서 그래프 상으로만은 이유를 발견하기 어렵지만 다른 참고 자료를 통해서 그 원인에 대해 찾아볼 수 있을 것 같습니다. 

info) 서브세션 2주차


[문제4]

  • 문제3의 calcTwoCharactersFromString()을 수정하여, calcTwoCharactersFromStringV2()를 작성합니다.
  • 수정된 동작은, 입력 파라메타 중 두번째와 세번째 글자가 같은 경우, result를 정수 -1로 리턴 합니다.

[풀이]

# calcTwoCharactersFromStringV2함수 정의
def calcTwoCharactersFromStringV2(iString, iCh1, iCh2):
    if iCh1 == iCh2: 
        return -1 #iCh1과 iCh2가 같은 경우 -1 반환
    else:
        #iString에서 iCh1의 개수를 카운트
        iCh1_count = iString.count(iCh1)
        #iString에서 iCh2의 개수를 카운트
        iCh2_count = iString.count(iCh2)
        return iCh1_count + iCh2_count 
        
#2,3번째 글자가 다른 경우
calcTwoCharactersFromStringV2("You only live once", 'o', 'Y')

#2,3번째 글자가 같은 경우
calcTwoCharactersFromStringV2("You only live once", 'o', 'o')
 

[문제5]

  • 문제3의 calcTwoCharactersFromString()을 수정하여, calcTwoCharactersFromStringV3()를 작성합니다.
  • 다른 동작은 동일하며, 문제3에서는 두 개의 글자를 iCh1와 iCh2로 입력 받았으나,이번 문제에서는 두 번째 입력 파라메타(iChList)가 글자들로 이루어진 리스트로 바뀐 사항만 다릅니다.

[풀이]

def calcTwoCharactersFromStringV3(iString:str, iChList:list):
    sum = 0
    for c in iChList:
        sum += iString.count(c)
    return sum
    
#리스트 원소가 2개인 경우
calcTwoCharactersFromStringV3("You only live once", ['o', 'Y'])

#리스트 원소가 4개인 경우
calcTwoCharactersFromStringV3("You only live once", ['o', 'Y', 'c', 'on'])

 

[문제6]

  • 입력 파라메타 2개를 받아서 result 값을 리턴하는 함수 mergeAndSortTwoList()를 작성합니다.
  • 입력 파라메타는 정수를 element로 갖는 리스트 입니다.
  • 리턴 값인 result는 입력 받은 두 개의 리스트에 포함된 모든 정수들을 합쳐서 하나의 리스트로 만들며, 중복된 정수는 하나만 포함되도록 하고,작은 숫자가 앞쪽에 있고 큰 숫자가 뒤쪽에 있도록 정렬된 형태로 만들어진 리스트 입니다.

[풀이]

#mergeAndSortTwoList()함수 정의
def mergeAndSortTwoList(list1, list2):
    #리스트를 '+'연산자로 합친 후 set(집합) 타입으로 중복을 제거하고 
    #다시 리스트 타입으로 변환
    result = list(set(list1 + list2)) 
    result.sort() #sort()를 통해 오름차순 정렬
    return result
    
#리스트 2개를 변수에 설정
list1 = [10, 30, 24, 36, 93, 78, 32, 56]
list2 = [83, 6, 24, 36, 93, 10, 44]

#함수 결과 출력
mergeAndSortTwoList(list1, list2)

 

[문제7]

  • 문제 6의 내부 동작을 수정하여 다음의 mergeAndSortTwoListReverse()를 작성합니다.
  • 수정된 동작은 문제6의 리턴 값인 result 리스트의 정렬 순서가 반대로 바뀌는 것으로,리턴 값인 result는 큰 숫자가 앞쪽에 있고 작은 숫자가 뒤쪽에 있도록 정렬된 형태로 만들어진 리스트 입니다.

[풀이]

#mergeAndSortTwoList()함수 정의
def mergeAndSortTwoListReverse(list1, list2):
    #리스트를 '+'연산자로 합친 후 set(집합) 타입으로 중복을 제거하고 
    #다시 리스트 타입으로 변환
    result = list(set(list1 + list2)) 
    result.sort(reverse = True) #sort()를 통해 오름차순 정렬
    return result
    
#리스트 2개를 변수에 설정
list1 = [10, 30, 24, 36, 93, 78, 32, 56]
list2 = [83, 6, 24, 36, 93, 10, 44]

#함수 결과 출력
mergeAndSortTwoListReverse(list1, list2)
 

[문제8]

  • 입력 파라메타 2개를 받아서 result 값을 리턴하는 함수 searchMatchedCharacter()를 작성합니다.
  • 첫번째 입력 파라메타는 문자열을 element로 갖는 리스트 이며, 두번째 입력 파라메타는 글자 하나 입니다.
  • 리턴 값인 result는 첫번째 입력 파라메타의 문자열 중, 문자열의 첫번째 글자가 두번째 입력 파라메타와 같은 경우, 해당 문자열들로 만들어진 리스트이며, 문자열의 순서는 alphabet의 순서대로 정렬된 상태 입니다.
  • 예를 들어, 첫번째 문자열이 다음과 같은 경우:
    • kingdoms = ['Bacteria','Protozoa','Chromista','Plantae','Fungi','Animalia']
  • 다음과 같이 두번째 입력 파라메타를 글자 P로 주는 경우:
    • searchMatchedCharacter(kingdoms, 'P')
  • 리턴 값 result는 다음과 같습니다.
    • ['Plantae', 'Protozoa']

[풀이]

def searchMatchedCharacter(list1, str1):
    result = [] #빈 리스트 생성
    for word in list1: #문자열 리스트에서 word 하나씩 for문으로 돌리기
        if word.startswith(str1): #해당 word가 str1으로 시작되면 True 반환
            result.append(word) #True이면 해당 word를 new_list에 추가 
    result.sort()
    return result
    
kingdoms = ['Bacteria','Protozoa','Chromista','Plantae','Fungi','Animalia']
searchMatchedCharacter(kingdoms, 'P')

 

[문제9]

  • 입력 파라메타 없이 정수 10개를 생성하여 리턴하는 함수 makeRandomTenIntegers()를 작성합니다.
  • 리턴 값인 result는 정수 리스트로써, 1부터 10사이의 숫자(1과 10포함)를 랜덤한 순서로 포함 합니다.
  • 따라서 result는 1부터 10사이의 숫자를 모두 포함하는 정수 리스트이며, 정수들의 순서는 규칙없이 랜덤이여야 합니다.

[풀이]

import random
import numpy as np

def makeRandomTenIntegers():
    int10 = list(np.arange(1,11)) #1부터 10까지의 리스트 생성
    result = random.sample(int10, 10) #리스트 내 원소들을 무작위로 섞어주기
    return result
    
makeRandomTenIntegers()

원래는 random.shuffle()을 사용하여 정수 리스트를 랜덤하게 섞어 주었으나 정답 파일과 비교한 결과 오답으로 판정되어 다른 방식을 사용하였습니다. shuffle 대신 random.sample()을 사용하였는데 shuffle과 sample의 차이를 찾아본 결과 둘 다 무작위로 섞어준다는 점은 동일하지만 다음의 차이가 있습니다. shuffle은 리스트 내에서 무작위로 섞어주며 sample의 경우에는 무작위로 섞어준 새로운 리스트를 반환하고 원본은 그대로 두게 됩니다. 또한 sample은 두번째 파라미터 값을 통해 원하는 element 수를 제한할 수 있으며  string, tuple 값들에 대해서도 셔플이 가능합니다.


[문제10]

  • 문제9의 함수를 확장해서 다음의 makeRandomIntegersExtended()를 작성합니다.
  • 첫번째 변경 사항으로 입력 파라메타가 2개 이며, 첫번째 입력 파라메타(iStart)는 랜덤 값의 시작이 되는 정수이고, 두번째 입력 파라메타(iEnd)는 랜덤 값의 마지막이 되는 정수 입니다.
  • 두 개의 입력 파라메타를 받은 함수는 다음의 경우에 대해서 정수 -1을 리턴하고 수행을 중지합니다.
    • iStart 혹은 iEnd가 0보다 작거나 같은 경우
      iStart와 iEnd가 같은 경우
      iStart가 iEnd 보다 작은 경우
  • 정상적인 입력 파라메타를 받은 경우는, 리턴 값인 result를 iStart부터 iEnd사이의 숫자(iStart와 iEnd포함)를 랜덤한 순서로 생성하여 채웁니다.
  • 따라서 result는 iStart부터 iEnd사이의 숫자를 모두 포함하는 정수 리스트이며, 순서는 규칙없이 랜덤이여야 합니다.

[풀이]

def makeRandomIntegersExtended(iStart, iEnd):
    #해당 조건을 condition 변수에 대입
    condition = (iStart <= 0) or (iEnd <= 0) or (iStart == iEnd) or (iStart < iEnd)
    #codition이 True이면 -1 리턴 아니면 result 리턴
    if condition:
        return -1
    else:
        int_list = list(np.arange(iEnd, iStart+1)) #iStart와 iEnd 사이 정수 리스트 생성
        random.sample(int_list, len(int_list)) #무작위로 섞어주기
        result = int_list
        return result
        
#조건에 걸리는 경우
makeRandomIntegersExtended(1, 10)

#조건에 걸리지 않는 경우
makeRandomIntegersExtended(10, 2)

[다른 풀이]

def makeRandomIntegersExtended(iStart, iEnd):
    if (iStart == iEnd) or (iStart < 0) or (iEnd < 0) or (iStart < iEnd):
        return -1
    else:
        randomList = []
        randomInt = random.randint(iEnd, iStart)
        for i in range(int(iStart-iEnd+1)):
            while randomInt in randomList:
                randomInt = random.randint(iEnd, iStart)
            randomList.append(randomInt)
        return randomList
 
 

 

'BACS > 서브세션' 카테고리의 다른 글

[서브세션] 서브세션 3주차  (0) 2022.11.06
[BACS] 서브세션 1주차  (0) 2022.09.27

Info) 정규세션 1주차 (강원도 소방 데이터)

Problem. 왼쪽의 원본 데이터를 가공하여 오른쪽 데이터 형태로 만들기


1. 데이터 불러오기

import pandas as pd

#관할서 센터별 소방용수 데이터
fire_df =  pd.read_csv('./관할서 센터별 소방용수 데이터.csv')

#관할서 센터별 소방용수 데이터 feature설명
feature_df = pd.read_excel('./관할서 센터별 소방용수 데이터.xls')

#정답 파일: 강원도 지역별 소방용수시설 별 개수
answer_df = pd.read_excel('./강원도 지역별 소방용수시설 별 개수.xlsx')

2. fire_df 확인 및 수정 

2-1. fire_df 컬럼명 변경

#feature_df의 컬럼명 리스트로 가져오기
column_names = list(feature_df.columns)

#fire_df의 컬럼명에 추가하여 변경
fire_df.columns = column_names

2-2. 결측치 확인 및 제거

#'소방용수구분명'의 결측치 확인
fire_df['소방용수구분명'].isna().sum() 

#'소방용수구분명'이 null인 행 제거
fire_df.dropna(subset=['소방용수구분명'], axis=0, inplace=True)

3. 파생변수 '지역' Column 만들기

#군구명과 동명을 합쳐 새로운 지역 컬럼 만들기
fire_df['지역'] = fire_df['구군명']+ ' ' + fire_df['동명']

4. One Hot Encoding 적용

#fire_df에서 필요한 컬럼만 추출 
fire_df = fire_df[['지역', '구군명', '동명', '소방용수구분명']]

#원핫 인코딩 적용하여 새로운 fire_df_new 생성
fire_df_new = pd.get_dummies(data = fire_df, columns = ['소방용수구분명'])

5. Column 이름 재설정 및 순서 재배치

#fire_df_new의 컬럼명 재설정
fire_df_new.columns = ['지역', '시군구', '읍면동', '급수탑', '기타', '소화전(지상식)', '소화전(지하식)', '저수조'] 

#컬럼 순서 재설정
fire_df_new = fire_df_new[['지역', '시군구', '읍면동', '소화전(지상식)', '소화전(지하식)', '급수탑', '저수조', '기타']]

6. groupby 통해 그룹별 집계

#groupby 통해 지역별 합계로 집계
fire_df_new = fire_df_new.groupby(['지역', '시군구', '읍면동']).sum()

#인덱스 초기화
fire_df_new.reset_index(inplace=True)

#최종 결과물 확인
fire_df_new

cf) 다른 방식 (crosstab 활용)

cross_tab_prop = pd.crosstab(index=[fire_df['지역'], fire_df['구군명'], fire_df['동명']],
                             columns=fire_df['소방용수구분명'],
                             normalize= False,
                             margins = False)
cross_tab_prop.reset_index(inplace = True)
cross_tab_prop.columns = ['지역', '구군명', '동명', '소화전(지상식)', '소화전(지하식)', '급수탑', '저수조', '기타']
cross_tab_prop

info) 정규세션 1주차 Pandas 실습 (World_2012 데이터 사용)


1. 데이터 확인

1-1. 데이터 불러오기

import pandas as pd
import numpy as np

df = pd.read_excel('./World_2012.xlsx')
df

1-2. 데이터 형태 확인

df.head(3) #상위 3개 확인
#df.tail() #하위 5개 확인

1-3. 데이터 크기 확인

df.shape #(행,열) 크기 확인

1-4. 데이터의 결측치 확인

df.isna().sum()

1-5.  ‘Continent’ 컬럼의 고유값 확인

df['Continent'].unique()

2. 결측치 처리

2-1. GDP 결측치 처리

- 대륙별 GDP 평균을 구하여 결측치에 대체

#{Continent : GDP평균} dictionary 생성
gdp_avg = dict(df.groupby('Continent')['GDP'].mean())

#해당 continent와 GDP가 null인 값에 대해 대륙별 GDP평균으로 대체
for (continent, gdp) in gdp_avg.items():
    df.loc[(df['Continent'] == continent) & (df['GDP'].isnull()), 'GDP'] = gdp 
    
#결측치 재확인
df['GDP'].isna().sum()

2-2. 나머지 결측치도 동일한 방식으로 처리

- 함수를 정의하여 결측치가 있는 모든 컬럼에 적용

#대륙별 feature들의 평균값 확인
df.groupby('Continent').mean()

#대륙별 평균값으로 결측치를 대체하는 함수 정의
def fillwithAverage(x):
    continent_avg = dict(df.groupby('Continent')[x].mean())
    for i in continent_avg.keys():
        df.loc[(df['Continent'] == i) & (df[x].isnull()), x] = continent_avg[i]
        
column_list =list(df.columns) #feature 목록 리스트

#GDP를 비롯한 Country, Continent, Population은 제외
remove_list = ['Country', 'Continent', 'Population', 'GDP'] 
column_list = [col for col in column_list if col not in remove_list]
print(column_list)

#반복문을 통해 함수 적용
for col in column_list:
    fillwithAverage(col)
    
#결측치 재확인
df.isna().sum()

3. 파생변수 생성

3-1. 1인당 GDP 소득인 'PCI'라는 파생변수 생성

- GDP Population 활용

df['PCI'] = df['GDP'] / df['Population']
df.head(3)

3-2. 기대수명인 'Life Expectancy'라는 파생변수 생성

- Male Life Expectancy, Femal Life Expectancy feature의 평균 활용

df['Life Expectancy'] = (df['Male Life Expectancy'] + df['Femal Life Expectancy']) / 2
df.tail(2)

4. 유럽 데이터 생성

4-1. 'Continet' 컬럼에서 '유럽'에 해당하는 것만 필터링하여 europe_data에 저장

- loc 함수 사용

europe_data = df.loc[df['Continent'] == '유럽', :]
europe_data.head(3)

4-2. europe_data Population 10,000,000이상인 나라만 필터링하여 europe_data 갱신

europe_data = europe_data[europe_data.Population >= 10000000]
europe_data.head(3)

4-3. europe_data에서 PCI(1인당 소득) 기준 상위 3개 국가 확인

europe_data.sort_values('PCI', ascending = False)[:3]

4-4. europe_data에서 PCI(1인당 소득)가 가장 작은 국가 확인

europe_data.sort_values('PCI', ascending = True)[:1]

5. 선진국 데이터 생성

5-1. Population 10,000,000이상이고 PCI 30,000이상인 국가를 필터링하여 developed_data라는 변수에 저장

developed_data = df.loc[(df['Population'] >= 10000000) & (df['PCI'] >= 30000),:]
developed_data

5-2. developed_data에서 Business TR 0.3이하 이거나, Internet 0.8 이상인 국가를 필터링

- Column Country Business, TR Loan, IR만 표시

developed_data.loc[(developed_data['Business TR'] <= 0.3) | (developed_data['Internet'] >= 0.8),:][['Country', 'Business TR', 'Loan IR']]

 

+ Recent posts