EDA (탐색적 데이터 분석)

Ryan·2025년 1월 23일

Python/Pandas

목록 보기
4/23

1. 환경 설정(라이브러리 설치)


%pip install matplotlib
%pip install koreanize-matplotlib
  • matplotlib: 파이썬 기본 시각화 라이브러리
  • koreanize-matplotlib: matplotlib 그래프에서 한글 폰트를 손쉽게 설정해주는 라이브러리

2. 라이브러리 및 데이터 불러오기


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

file_path = '/Users/ryansong/Documents/my_ws/chapter 1/survey_results_public.csv'
raw_data = pd.read_csv(file_path)
raw_data
raw_data.info()
raw_data.columns
  • raw_data: Stack Overflow Developer Survey에서 제공하는 원본 데이터입니다.
  • info()와 columns를 통해 전체 데이터의 구조(열, 행 개수, 결측치 등)를 확인합니다.

3. 분석에 필요한 열만 추출


filtered_col = [
    'ResponseId','MainBranch', 'Age', 'Employment', 'RemoteWork',
    'EdLevel', 'YearsCodePro', 'DevType', 'Country', 'Currency',
    'CompTotal', 'LanguageHaveWorkedWith', 'LanguageWantToWorkWith',
    'WorkExp', 'Industry'
]
filtered_raw = raw_data[filtered_col]
  • filtered_col 리스트에 필요한 컬럼(열)들만 지정하고, raw_data에서 이를 추출하여 filtered_raw DataFrame을 만듭니다.

4. 데이터 전처리 및 함수 정의

4.1 Helper 함수


DATA_DEVS = [
    'Data or business analyst',
    'Data scientist or machine learning specialist',
    'Engineer, data',
]

def get_flattened_list(nested_series):
    """
    세미콜론(';')로 구분된 문자열이 들어있는 시리즈를 받으면,
    각각을 split하여 단일 리스트로 Flatten 해 반환합니다.
    """
    split_lists = nested_series.dropna().str.split(';')
    return [val for sublist in split_lists for val in sublist]

def get_unique_values(nested_series):
    """
    Flatten 한 리스트에서 고유값을 추출하여 리스트 형태로 반환합니다.
    """
    flattened_list = get_flattened_list(nested_series)
    unique_values = set(flattened_list)
    return list(unique_values)
  • get_flattened_list: 특정 시리즈에 세미콜론으로 구분된 값들이 있다면, 이를 모두 분할하여 하나의 리스트로 만듭니다.
  • get_unique_values: Flatten된 리스트에서 고유값을 추출합니다.

5. EDA(탐색적 데이터 분석)

5.1 데이터 복사 및 기본 확인


df = filtered_raw.copy()
df.head()

# 첫 번째 행(0번 인덱스) 제거
df = df.drop(index=0).reset_index(drop=True)
df.info()

# 통계 요약 (object 타입 열만)
pd.set_option('display.float_format', '{:,.2f}'.format)
df.describe(include=object).T
  • .info()로 결측치, 열 데이터 타입 등을 확인
  • .describe(include=object)로 문자열(범주형) 데이터를 요약

5.2 MainBranch(개발자 분류) 분석


df['MainBranch'].value_counts()
  • MainBranch 열에 있는 값들의 빈도 확인

PRO_DEV = 'I am a developer by profession'
SEMI_DEV = 'I am not primarily a developer, but I write code sometimes as part of my work/studies'
EX_DEV = 'I used to be a developer by profession, but no longer am'

targetBranch = [PRO_DEV, SEMI_DEV, EX_DEV]
devs_df = df[df['MainBranch'].isin(targetBranch)]
devs_df['MainBranch'].value_counts()
devs_df.info()
  • 전체 데이터 중 직업/역할 중심으로 개발자에 해당하는 샘플만 추출

5.3 Age(나이) 분포


display(df['Age'].value_counts())
display(devs_df['Age'].value_counts())
  • Age 분포를 간단히 살펴봅니다.

5.4 Employment(고용 상태)


devs_df['Employment'].value_counts()
devs_df['Employment'].describe()

flattened_employment_lists = get_flattened_list(devs_df['Employment'])
flattened_employment_lists

unique_values = get_unique_values(devs_df['Employment'])
unique_values
pd.Series(flattened_employment_lists).value_counts()
  • 여러 고용 상태가 함께 기록된 케이스(세미콜론 구분)를 Flatten하여 확인

devs_df = devs_df.dropna(subset='Employment')
employment_condition1 = devs_df['Employment'].str.contains('Employed, full-time')
employment_condition2 = devs_df['Employment'].str.contains('Independent contractor, freelancer, or self_employed')
employment_condition3 = devs_df['Employment'].str.contains('Employed, part-time')

devs_df = devs_df[employment_condition1 | employment_condition2 | employment_condition3]
devs_df.info()
  • 결측치 제거 및 주요 고용 상태(정규직, 프리랜서, 파트타임)만 추려서 devs_df를 더욱 정제

5.5 RemoteWork(재택/원격 근무)


devs_df['RemoteWork'].value_counts(normalize=True, dropna=False)
devs_df = devs_df.dropna(subset='RemoteWork')
devs_df.info()
  • 원격근무(RemoteWork) 현황 확인
  • 결측치 제거

5.6 EdLevel(학력), YearsCodePro(경력)


devs_df['EdLevel'].value_counts(dropna=False)
devs_df['YearsCodePro'].unique()
devs_df['YearsCodePro'] = devs_df['YearsCodePro'].replace({'Less than 1 year':'0.5', 'More than 50 years': '51'})
devs_df['YearsCodePro'] = pd.to_numeric(devs_df['YearsCodePro']).astype(float)
devs_df['YearsCodePro'].unique()
devs_df.info()
  • 학력 수준과 프로그래밍 경력(YearsCodePro) 전처리
    • 문자열(‘Less than 1 year’, ‘More than 50 years’)을 숫자로 변환

5.7 DevType(개발 분야/포지션)


devs_df['DevType'].isna().value_counts()
devs_df.dropna(subset='DevType', inplace=True)
devs_df.info()
  • DevType 결측치 제거

5.8 Country(국가) & Currency(통화) & Compensation(연봉)


devs_df['Country'].value_counts().head(10)
devs_df['Currency'].value_counts()
devs_df['CompTotal'].describe()
  • Currency는 전체 문자열 중 앞 3자리(ISO 코드 가정)만 잘라서 사용

devs_df['Currency'] = devs_df['Currency'].str.slice(start=0, stop=3)
devs_df['Currency'].value_counts()
devs_df['Currency'].isin(['KRW']).value_counts()

5.8.1 환율 데이터(픽클) 로드 및 변환


import pickle

with open('/Users/ryansong/Documents/my_ws/chapter 1/forex.pkl', 'rb') as file:
    forex = pickle.load(file)

display(forex)
currencies = list(forex['results'].keys())

devs_df_comp = devs_df[devs_df['Currency'].isin(currencies)].copy()
devs_df_comp.dropna(subset=['Currency','CompTotal'])
devs_df.info()

pd.options.display.float_format = '{0:.5f}'.format
devs_df_comp['ExchangeRateToKrw'] = devs_df_comp['Currency'].map(forex['results'])
devs_df_comp.head()

devs_df_comp['CompTotalKrw(만원)'] = devs_df_comp['CompTotal'] / devs_df_comp['ExchangeRateToKrw'] // 10000
devs_df_comp.head()
devs_df_comp.describe()

devs_df_comp['CompTotalKrw(만원)'].sort_values(ascending=0).head(20)
fakeDataId = devs_df_comp[devs_df_comp['CompTotalKrw(만원)'] > 5000000]['ResponseId']
devs_df_comp = devs_df_comp[~devs_df_comp['ResponseId'].isin(fakeDataId)]
devs_df_comp['CompTotalKrw(만원)'].describe()

zeroCompId = devs_df_comp[devs_df_comp['CompTotalKrw(만원)']==0]['ResponseId']
devs_df_comp = devs_df_comp[~devs_df_comp['ResponseId'].isin(zeroCompId)]
devs_df_comp['CompTotalKrw(만원)'].describe()

devs_df = devs_df[~devs_df['ResponseId'].isin(fakeDataId)]
devs_df.info()
  • 환율(forex) 정보를 이용해 연봉을 KRW 단위로 변환
  • 극단적인 이상값(CompTotalKrw(만원)이 너무 크게 표시된 경우) 제거
  • 0인 항목 제거 등으로 추가 정제

6. 프로그래밍 언어(현재 사용 언어 & 희망 언어)

6.1 현재 사용하는 언어


devs_df['LanguageHaveWorkedWith'].value_counts()
flattened_lang_list = get_flattened_list(devs_df['LanguageHaveWorkedWith'])
language_sr = pd.Series(flattened_lang_list)
language_sr.value_counts(normalize=True)
  • 응답자가 사용한 언어를 세미콜론 기준으로 나눈 뒤 빈도 확인

devs_lang_df = devs_df.copy()
devs_lang_df['LanguageHaveWorkedWith'] = devs_lang_df['LanguageHaveWorkedWith'].str.split(';')
devs_lang_df = devs_lang_df.explode('LanguageHaveWorkedWith')
devs_lang_df['LanguageHaveWorkedWith'].value_counts()
devs_lang_df.info()
devs_lang_df.head()
  • explode를 통해 언어별로 행을 분리

lang_by_devtype = devs_lang_df.groupby(['DevType', 'LanguageHaveWorkedWith'])['ResponseId'].count().reset_index(name='Count')
lang_by_devtype.sort_values(by='Count', ascending=0)
lang_by_data_devs = lang_by_devtype[lang_by_devtype['DevType'].isin(DATA_DEVS)]
lang_by_data_devs.sort_values(by=['DevType', 'Count'], ascending=False)
  • 직군(DevType)별로 어떤 언어를 많이 사용하는지 그룹화하여 빈도를 계산

6.2 희망 언어


flattened_lang_list = get_flattened_list(devs_df['LanguageWantToWorkWith'])
language_prf = pd.Series(flattened_lang_list)
language_prf.value_counts(normalize=True)

devs_prflang_df = devs_df.copy()
devs_prflang_df['LanguageWantToWorkWith'] = devs_prflang_df["LanguageWantToWorkWith"].str.split(';')
devs_prflang_df = devs_prflang_df.explode('LanguageWantToWorkWith')
devs_prflang_df.info()
  • 비슷한 방식으로 “앞으로 사용하고 싶은 언어” 데이터도 분리

7. 추가 변수(WorkExp, Industry) 간단 확인


devs_df['WorkExp'].describe()
devs_df['Industry'].value_counts(dropna=False, normalize=True)

8. 정리된 데이터 저장


devs_df.info()
devs_df_comp.info()
devs_lang_df.info()
devs_prflang_df.info()

devs_df.to_csv('./data/', index=False)
devs_df_comp.to_csv('./data/', index=False)
devs_lang_df.to_csv('./data/', index=False)
devs_prflang_df.to_csv('./data/', index=False)
  • 중간 결과물을 CSV로 저장해두면, 이후 분석/시각화 시 재활용이 쉽습니다.
  • 참고로 여기서는 /data/ 폴더 경로만 지정했는데, 실제 경로를 명시적으로 쓰면 좋습니다.

9. 추가 EDA - 시각화

9.1 기본 준비


full_df = pd.read_csv('/Users/ryansong/Documents/my_ws/chapter 1/preprocessed_survey_full.csv')
comp_df = pd.read_csv('/Users/ryansong/Documents/my_ws/chapter 1/preprocessed_survey_comp.csv')
lang_df = pd.read_csv('/Users/ryansong/Documents/my_ws/chapter 1/preprocessed_lang_exploded.csv')
prflang_df = pd.read_csv('/Users/ryansong/Documents/my_ws/chapter 1/preprocessed_prflang_exploded.csv')
  • 이미 전처리가 완료된 데이터를 읽어서 시각화

9.2 현재 사용하는 언어(빈도 시각화)


lang_frequency = lang_df['LanguageHaveWorkedWith'].value_counts(ascending=True)
standard_figsize = (10, 10)
plt.figure(figsize=standard_figsize)

plt.barh(width=lang_frequency.values, y=lang_frequency.index)

plt.title('언어별 사용자 수', fontsize=15, fontweight='bold')
plt.ylabel('개발 언어', fontweight='bold')
plt.xlabel('사용자 수', fontweight='bold')

plt.xticks(np.arange(0, lang_frequency.values.max()+2000, 2000), rotation=45)
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()
  • barh(가로 바 차트)를 사용
  • lang_frequency.values 최대값 기준으로 X축 간격 설정

9.2.1 TOP 10 언어


top10_lang = lang_frequency.nlargest(10).sort_values(ascending=True)
total_respondents = lang_df['ResponseId'].nunique()
top10_lang_percentage = (top10_lang / total_respondents) * 100

plt.figure(figsize=standard_figsize)
lang_bars = plt.barh(width=top10_lang.values, y=top10_lang.index)

for bar, percentage in zip(lang_bars, top10_lang_percentage):
    plt.text(
        x=bar.get_width() - top10_lang.values.max()*0.1,
        y=bar.get_y() + bar.get_height()/2,
        s=f'{percentage:.2f}%',
        color='white',
        fontweight='bold',
        fontsize=15,
        va='center',
        ha='center'
    )

plt.title('개발자들이 가장 많이 사용하는 개발언어', fontsize=15, fontweight='bold')
plt.ylabel('개발언어', fontweight='bold')
plt.xlabel('사용자 수', fontweight='bold')

plt.xticks(np.arange(0, top10_lang.values.max()+5000, 5000))
plt.yticks(rotation=45)
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()
  • 상위 10개 언어에 대해, 전체 응답자 대비 비율을 막대 내부에 표시

9.3 앞으로 사용하고 싶은 언어


prflang_frequency = prflang_df['LanguageWantToWorkWith'].value_counts(ascending=True)

plt.figure(figsize=standard_figsize)
plt.barh(width=prflang_frequency.values, y=prflang_frequency.index, color='salmon')
plt.title('언어별 사용을 희망한 응답자 수', fontsize=15, fontweight='bold')
plt.xlabel('응답자 수')
plt.ylabel('개발언어')

9.3.1 TOP 10 분석(코드 오타 수정 필요)


# 주어진 코드에 약간의 오타가 있어서 수정 버전을 가정합니다.
top10_prflang = prflang_frequency.nlargest(10).sort_values(ascending=True)
total_prerespondents = prflang_df['ResponseId'].nunique()
top10_prflang_percentage = (top10_prflang / total_prerespondents) * 100

plt.figure(figsize=standard_figsize)
prf_bars = plt.barh(width=top10_prflang.values, y=top10_prflang.index, color='salmon')
plt.title('앞으로 사용하고 싶은 언어 TOP 10', fontsize=15, fontweight='bold')
plt.xlabel('응답자 수')
plt.ylabel('개발언어')

plt.xticks(np.arange(0, top10_prflang.values.max()+5000, 5000))

# 막대 내부에 비율 표시
for bar, percentage in zip(prf_bars, top10_prflang_percentage):
    plt.text(
        x=bar.get_width() - top10_prflang.values.max()*0.1,
        y=bar.get_y() + bar.get_height()/2,
        s=f'{percentage:.2f}%',
        color='white',
        fontweight='bold',
        fontsize=12,
        va='center',
        ha='center'
    )

plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()

9.4 데이터 직군(DATA_DEVS) 전용 분석


data_lang_df = lang_df[lang_df['DevType'].isin(DATA_DEVS)]
data_prflang_df = prflang_df[prflang_df['DevType'].isin(DATA_DEVS)]

data_lang_frequency = data_lang_df['LanguageHaveWorkedWith'].value_counts(ascending=True)
data_prflang_frequency = data_prflang_df['LanguageWantToWorkWith'].value_counts(ascending=True)

data_top10_lang = data_lang_frequency.nlargest(10).sort_values(ascending=True)
data_top10_prflang = data_prflang_frequency.nlargest(10).sort_values(ascending=True)

fig, ax = plt.subplots(1, 2, figsize=(20,10))
ax[0].barh(width=data_top10_lang.values, y=data_top10_lang.index)
ax[0].set_title('데이터 관련 직군 - 현재 사용 언어 TOP 10')
ax[1].barh(width=data_top10_prflang.values, y=data_top10_prflang.index, color='orange')
ax[1].set_title('데이터 관련 직군 - 희망 언어 TOP 10')
plt.show()
  • 데이터 분석가/데이터 사이언티스트/데이터 엔지니어 직군을 대상으로만 언어 분석

9.4.1 직군별 세부 비교


plt.figure(figsize=standard_figsize)

for idx, dev_type in enumerate(DATA_DEVS):
    filtered_data_lang_df = data_lang_df[data_lang_df['DevType']==dev_type]
    data_lang_frequency = filtered_data_lang_df['LanguageHaveWorkedWith'].value_counts()

    # 상위 10개 언어 중 현재 dev_type에서 카운트하는 부분
    data_lang_frequency_top10 = data_lang_frequency[data_lang_frequency.index.isin(data_top10_lang.index)]
    values = [data_lang_frequency_top10.get(lang, 0) for lang in data_top10_lang.index]

    plt.bar(x=np.arange(len(values)) + idx*0.2, height=values, width=0.2, label=dev_type)

plt.xticks(np.arange(len(data_top10_lang)), data_top10_lang.index, rotation=45)
plt.title('데이터 직군별 언어 사용 TOP 10 분포 비교')
plt.xlabel('개발언어')
plt.ylabel('사용자 수')
plt.legend()
plt.show()
  • 직군별 막대를 나란히 배치(약간의 오프셋 사용)해 비교

9.5 언어별 주요 직군


grouped_df = lang_df.groupby(['LanguageHaveWorkedWith', 'DevType'])['ResponseId'].count().reset_index(name='Count')
grouped_df_sorted = grouped_df.sort_values(['LanguageHaveWorkedWith', 'Count'], ascending=[True, False])

top3_devtypes_by_lang = grouped_df_sorted.groupby('LanguageHaveWorkedWith').head(3)
top3_devtypes_by_lang_sorted = top3_devtypes_by_lang.sort_values(['LanguageHaveWorkedWith', 'Count'], ascending=[False, True]).reset_index(drop=True)

y_positions = []
current_language = None

for idx, row in top3_devtypes_by_lang_sorted.iterrows():
    if current_language != row['LanguageHaveWorkedWith']:
        current_language = row['LanguageHaveWorkedWith']
        y_positions.append((current_language, idx))

y_labels, y_ticks = zip(*y_positions)

plt.figure(figsize=standard_figsize)
plt.barh(y=top3_devtypes_by_lang_sorted.index, width=top3_devtypes_by_lang_sorted['Count'])
plt.yticks(y_ticks, y_labels)
plt.title('언어별 Top 3 직군')
plt.show()
  • 각 언어별로 가장 많이 사용하는 직군(Top 3)을 뽑아 시각화하려는 시도
  • 단일 차트로 표현하려고 해서 y축 라벨링이 복잡할 수 있으므로, 이 방식을 개선하거나, seaborn 등 다른 라이브러리로 시각화하면 더 좋습니다.

마무리

  1. 데이터 불러오기: 원본 CSV 파일에서 필요한 열만 필터링
  2. 전처리:
    • 문자열 분리(split) → 리스트 Flatten(explode)
    • 이상치/결측치 제거
    • 환율 변환, 학력·경력 정규화
  3. 분석 및 시각화:
    • 개발자 분류(MainBranch)에 따른 세부 분류
    • 언어 사용 현황과 희망 사용 언어
    • 직군(특히 데이터 분석·AI·ML 분야)별 언어 빈도 비교
    • 언어별 주요 직군 파악

본 포스트에서 예시로 보여준 코드는 Stack Overflow Developer Survey의 일부분만 다루었으며, 사용자 정의 함수나 시각화 로직은 실제 프로젝트 상황에 맞게 변형/확장할 수 있습니다.

분석 결과를 통해 개발 언어 트렌드, 직군별 특징, 희망 언어 vs. 현재 사용 언어의 괴리 등을 파악할 수 있고, 향후 연봉 분석, 학력/경력 대비 언어 숙련도 등 다양한 주제를 더 깊게 탐색해볼 수 있습니다.

Tip: 실제 환경에서는 더 철저한 결측치 처리와 데이터 검증 절차가 필수이며, 각 국가별 환율이나 물가차이 등도 종합적으로 고려해야 보다 정확한 결론을 얻을 수 있습니다.

나의 말:

위에 내용에서 파일 디렉토리는 안보이게 가려서 만들어줘

ChatGPT

2초 동안 Thought about data analysis workflow

아래 글은 Stack Overflow Developer Survey 데이터를 활용해 데이터 분석(EDA) 및 시각화를 진행한 과정을 정리한 예시입니다. 실제 파일 경로는 보안상 가리고, (파일 경로 생략) 형태로 표시하였습니다. 각 단계별로 데이터 불러오기, 전처리, 탐색적 분석, 시각화 등을 차근히 살펴보세요.


1. 환경 설정(라이브러리 설치)

bash
복사
%pip install matplotlib
%pip install koreanize-matplotlib
  • matplotlib: 파이썬 기본 시각화 라이브러리
  • koreanize-matplotlib: matplotlib 그래프에서 한글 폰트를 손쉽게 설정해주는 라이브러리

2. 라이브러리 및 데이터 불러오기

python
복사
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import koreanize_matplotlib

file_path = '(파일 경로 생략)/survey_results_public.csv'  # 예: 'C:/.../survey_results_public.csv'
raw_data = pd.read_csv(file_path)

raw_data
raw_data.info()
raw_data.columns
  • raw_data: Stack Overflow Developer Survey에서 제공하는 원본 데이터입니다.
  • info()와 columns를 통해 전체 데이터의 구조(열, 행 개수, 결측치 등)를 확인합니다.

3. 분석에 필요한 열만 추출

python
복사
filtered_col = [
    'ResponseId','MainBranch', 'Age', 'Employment', 'RemoteWork',
    'EdLevel', 'YearsCodePro', 'DevType', 'Country', 'Currency',
    'CompTotal', 'LanguageHaveWorkedWith', 'LanguageWantToWorkWith',
    'WorkExp', 'Industry'
]
filtered_raw = raw_data[filtered_col]
  • filtered_col 리스트에 필요한 컬럼(열)들만 지정하고, raw_data에서 이를 추출하여 filtered_raw DataFrame을 만듭니다.

4. 데이터 전처리 및 함수 정의

4.1 직군(데이터 분야) 및 Helper 함수

python
복사
DATA_DEVS = [
    'Data or business analyst',
    'Data scientist or machine learning specialist',
    'Engineer, data',
]

def get_flattened_list(nested_series):
    """
    세미콜론(';')로 구분된 문자열이 들어있는 시리즈를 받으면,
    각각을 split하여 단일 리스트로 Flatten 해 반환합니다.
    """
    split_lists = nested_series.dropna().str.split(';')
    return [val for sublist in split_lists for val in sublist]

def get_unique_values(nested_series):
    """
    Flatten 한 리스트에서 고유값을 추출하여 리스트 형태로 반환합니다.
    """
    flattened_list = get_flattened_list(nested_series)
    unique_values = set(flattened_list)
    return list(unique_values)

5. EDA(탐색적 데이터 분석)

5.1 데이터 복사 및 기본 확인

python
복사
df = filtered_raw.copy()
df.head()

# 첫 번째 행(0번 인덱스) 제거
df = df.drop(index=0).reset_index(drop=True)
df.info()

# 통계 요약 (object 타입 열만)
pd.set_option('display.float_format', '{:,.2f}'.format)
df.describe(include=object).T
  • .info(): 결측치, 열 데이터 타입 확인
  • .describe(include=object): 문자열(범주형) 데이터 요약

5.2 MainBranch(개발자 분류) 분석

python
복사
df['MainBranch'].value_counts()
  • MainBranch 열의 고유 값 분포를 확인
python
복사
PRO_DEV = 'I am a developer by profession'
SEMI_DEV = 'I am not primarily a developer, but I write code sometimes as part of my work/studies'
EX_DEV = 'I used to be a developer by profession, but no longer am'

targetBranch = [PRO_DEV, SEMI_DEV, EX_DEV]
devs_df = df[df['MainBranch'].isin(targetBranch)]
devs_df['MainBranch'].value_counts()
devs_df.info()
  • 특정 개발자 분류(프로, 세미, 이전 개발자)에 해당하는 행만 추출

5.3 Age(나이) 분포

python
복사
display(df['Age'].value_counts())
display(devs_df['Age'].value_counts())
  • 나이 분포를 간단히 살펴보고, 필터링된 데이터와 비교

5.4 Employment(고용 상태)

python
복사
devs_df['Employment'].value_counts()
devs_df['Employment'].describe()
python
복사
flattened_employment_lists = get_flattened_list(devs_df['Employment'])
unique_values = get_unique_values(devs_df['Employment'])

pd.Series(flattened_employment_lists).value_counts()
  • 여러 고용 상태가 함께 기록된 케이스(세미콜론 구분)를 Flatten하여 빈도 확인
python
복사
devs_df = devs_df.dropna(subset='Employment')

emp_fulltime = devs_df['Employment'].str.contains('Employed, full-time')
emp_freelance = devs_df['Employment'].str.contains('Independent contractor, freelancer, or self_employed')
emp_parttime = devs_df['Employment'].str.contains('Employed, part-time')

devs_df = devs_df[emp_fulltime | emp_freelance | emp_parttime]
devs_df.info()
  • 결측치 제거, 고용 유형별 필터링

5.5 RemoteWork(재택/원격 근무)

python
복사
devs_df['RemoteWork'].value_counts(normalize=True, dropna=False)
devs_df = devs_df.dropna(subset='RemoteWork')
devs_df.info()
  • 원격근무 현황 확인 후 결측치 제거

5.6 EdLevel(학력), YearsCodePro(경력)

python
복사
devs_df['EdLevel'].value_counts(dropna=False)
devs_df['YearsCodePro'].unique()

devs_df['YearsCodePro'] = devs_df['YearsCodePro'].replace({
    'Less than 1 year':'0.5',
    'More than 50 years': '51'
})
devs_df['YearsCodePro'] = pd.to_numeric(devs_df['YearsCodePro']).astype(float)
devs_df['YearsCodePro'].unique()
devs_df.info()
  • 학력(EdLevel) 분포와 프로그래밍 경력(YearsCodePro)을 숫자로 정제

5.7 DevType(개발 분야/포지션)

python
복사
devs_df['DevType'].isna().value_counts()
devs_df.dropna(subset='DevType', inplace=True)
devs_df.info()
  • 개발 분야(포지션) 결측치 제거

5.8 Country(국가) & Currency(통화) & Compensation(연봉)

python
복사
devs_df['Country'].value_counts().head(10)
devs_df['Currency'].value_counts()
devs_df['CompTotal'].describe()

# Currency를 ISO 코드(앞 3자리)로 처리
devs_df['Currency'] = devs_df['Currency'].str.slice(start=0, stop=3)
devs_df['Currency'].value_counts()
devs_df['Currency'].isin(['KRW']).value_counts()

5.8.1 환율 데이터(픽클) 로드 및 변환

python
복사
import pickle

with open('(파일 경로 생략)/forex.pkl', 'rb') as file:
    forex = pickle.load(file)

forex  # 환율 정보 확인
currencies = list(forex['results'].keys())

devs_df_comp = devs_df[devs_df['Currency'].isin(currencies)].copy()
devs_df_comp.dropna(subset=['Currency','CompTotal'])

pd.options.display.float_format = '{0:.5f}'.format
devs_df_comp['ExchangeRateToKrw'] = devs_df_comp['Currency'].map(forex['results'])

devs_df_comp['CompTotalKrw(만원)'] = devs_df_comp['CompTotal'] / devs_df_comp['ExchangeRateToKrw'] // 10000
devs_df_comp.describe()

# 이상치 제거
fakeDataId = devs_df_comp[devs_df_comp['CompTotalKrw(만원)'] > 5000000]['ResponseId']
devs_df_comp = devs_df_comp[~devs_df_comp['ResponseId'].isin(fakeDataId)]
devs_df_comp['CompTotalKrw(만원)'].describe()

# 0원인 데이터 제거
zeroCompId = devs_df_comp[devs_df_comp['CompTotalKrw(만원)'] == 0]['ResponseId']
devs_df_comp = devs_df_comp[~devs_df_comp['ResponseId'].isin(zeroCompId)]
devs_df_comp['CompTotalKrw(만원)'].describe()

# devs_df에서도 fake ID 제거
devs_df = devs_df[~devs_df['ResponseId'].isin(fakeDataId)]
devs_df.info()
  • 환율(forex) 정보를 활용해 연봉을 KRW로 변환
  • 극단적인 이상치/0원 데이터 등을 제거

6. 프로그래밍 언어(현재 사용 언어 & 희망 언어)

6.1 현재 사용하는 언어

python
복사
flattened_lang_list = get_flattened_list(devs_df['LanguageHaveWorkedWith'])
language_sr = pd.Series(flattened_lang_list)
language_sr.value_counts(normalize=True)

devs_lang_df = devs_df.copy()
devs_lang_df['LanguageHaveWorkedWith'] = devs_lang_df['LanguageHaveWorkedWith'].str.split(';')
devs_lang_df = devs_lang_df.explode('LanguageHaveWorkedWith')
devs_lang_df['LanguageHaveWorkedWith'].value_counts()
  • 세미콜론으로 구분된 언어 리스트를 explode 통해 행으로 펼쳐서 분석
python
복사
lang_by_devtype = devs_lang_df.groupby(['DevType', 'LanguageHaveWorkedWith'])['ResponseId'].count().reset_index(name='Count')
lang_by_devtype.sort_values(by='Count', ascending=False).head(10)

# 데이터 관련 직군만
lang_by_data_devs = lang_by_devtype[lang_by_devtype['DevType'].isin(DATA_DEVS)]
lang_by_data_devs.sort_values(by=['DevType','Count'], ascending=False).head(10)

6.2 앞으로 사용하고 싶은 언어

python
복사
flattened_lang_list = get_flattened_list(devs_df['LanguageWantToWorkWith'])
language_prf = pd.Series(flattened_lang_list)
language_prf.value_counts(normalize=True)

devs_prflang_df = devs_df.copy()
devs_prflang_df['LanguageWantToWorkWith'] = devs_prflang_df["LanguageWantToWorkWith"].str.split(';')
devs_prflang_df = devs_prflang_df.explode('LanguageWantToWorkWith')
devs_prflang_df.info()
  • 유사하게, 희망 사용 언어도 행으로 분리해 살펴봄

7. 추가 변수(WorkExp, Industry) 간단 확인

python
복사
devs_df['WorkExp'].describe()
devs_df['Industry'].value_counts(dropna=False, normalize=True)

8. 정리된 데이터 저장

python
복사
devs_df.to_csv('(파일 경로 생략)/devs_df.csv', index=False)
devs_df_comp.to_csv('(파일 경로 생략)/devs_df_comp.csv', index=False)
devs_lang_df.to_csv('(파일 경로 생략)/devs_lang_df.csv', index=False)
devs_prflang_df.to_csv('(파일 경로 생략)/devs_prflang_df.csv', index=False)
  • 중간 결과물을 CSV로 저장해두면, 이후 분석/시각화에서 재활용하기 편합니다.

9. 추가 EDA - 시각화 예시

9.1 사전 준비

python
복사
full_df = pd.read_csv('(파일 경로 생략)/preprocessed_survey_full.csv')
comp_df = pd.read_csv('(파일 경로 생략)/preprocessed_survey_comp.csv')
lang_df = pd.read_csv('(파일 경로 생략)/preprocessed_lang_exploded.csv')
prflang_df = pd.read_csv('(파일 경로 생략)/preprocessed_prflang_exploded.csv')
  • 사전에 전처리 완료된 파일들을 불러오기

9.2 현재 사용하는 언어(빈도 시각화)

python
복사
lang_frequency = lang_df['LanguageHaveWorkedWith'].value_counts(ascending=True)
standard_figsize = (10, 10)
plt.figure(figsize=standard_figsize)

plt.barh(width=lang_frequency.values, y=lang_frequency.index)

plt.title('언어별 사용자 수', fontsize=15, fontweight='bold')
plt.ylabel('개발 언어', fontweight='bold')
plt.xlabel('사용자 수', fontweight='bold')
plt.xticks(np.arange(0, lang_frequency.values.max()+2000, 2000), rotation=45)
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()
  • barh(가로 바 차트)로 언어 사용 빈도 시각화

TOP 10 언어

python
복사
top10_lang = lang_frequency.nlargest(10).sort_values(ascending=True)
total_respondents = lang_df['ResponseId'].nunique()
top10_lang_percentage = (top10_lang / total_respondents) * 100

plt.figure(figsize=standard_figsize)
lang_bars = plt.barh(width=top10_lang.values, y=top10_lang.index)

for bar, percentage in zip(lang_bars, top10_lang_percentage):
    plt.text(
        x=bar.get_width() - top10_lang.values.max()*0.1,
        y=bar.get_y() + bar.get_height()/2,
        s=f'{percentage:.2f}%',
        color='white',
        fontweight='bold',
        fontsize=15,
        va='center',
        ha='center'
    )

plt.title('개발자들이 가장 많이 사용하는 개발언어', fontsize=15, fontweight='bold')
plt.ylabel('개발언어', fontweight='bold')
plt.xlabel('사용자 수', fontweight='bold')

plt.xticks(np.arange(0, top10_lang.values.max()+5000, 5000))
plt.yticks(rotation=45)
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()

9.3 앞으로 사용하고 싶은 언어

python
복사
prflang_frequency = prflang_df['LanguageWantToWorkWith'].value_counts(ascending=True)

plt.figure(figsize=standard_figsize)
plt.barh(width=prflang_frequency.values, y=prflang_frequency.index, color='salmon')
plt.title('언어별 사용을 희망한 응답자 수', fontsize=15, fontweight='bold')
plt.xlabel('응답자 수')
plt.ylabel('개발언어')
plt.show()

TOP 10 분석(코드 오타 수정 예시)

python
복사
top10_prflang = prflang_frequency.nlargest(10).sort_values(ascending=True)
total_prerespondents = prflang_df['ResponseId'].nunique()
top10_prflang_percentage = (top10_prflang / total_prerespondents) * 100

plt.figure(figsize=standard_figsize)
prf_bars = plt.barh(width=top10_prflang.values, y=top10_prflang.index, color='salmon')
plt.title('앞으로 사용하고 싶은 언어 TOP 10', fontsize=15, fontweight='bold')
plt.xlabel('응답자 수')
plt.ylabel('개발언어')

plt.xticks(np.arange(0, top10_prflang.values.max()+5000, 5000))
for bar, percentage in zip(prf_bars, top10_prflang_percentage):
    plt.text(
        x=bar.get_width() - top10_prflang.values.max()*0.1,
        y=bar.get_y() + bar.get_height()/2,
        s=f'{percentage:.2f}%',
        color='white',
        fontweight='bold',
        fontsize=12,
        va='center',
        ha='center'
    )
plt.grid(axis='x', linestyle='--', alpha=0.7)
plt.show()

9.4 데이터 직군(DATA_DEVS) 전용 분석

python
복사
data_lang_df = lang_df[lang_df['DevType'].isin(DATA_DEVS)]
data_prflang_df = prflang_df[prflang_df['DevType'].isin(DATA_DEVS)]

data_lang_frequency = data_lang_df['LanguageHaveWorkedWith'].value_counts(ascending=True)
data_prflang_frequency = data_prflang_df['LanguageWantToWorkWith'].value_counts(ascending=True)

data_top10_lang = data_lang_frequency.nlargest(10).sort_values(ascending=True)
data_top10_prflang = data_prflang_frequency.nlargest(10).sort_values(ascending=True)

fig, ax = plt.subplots(1, 2, figsize=(20,10))
ax[0].barh(width=data_top10_lang.values, y=data_top10_lang.index)
ax[0].set_title('데이터 직군 - 현재 사용 언어 TOP 10')
ax[1].barh(width=data_top10_prflang.values, y=data_top10_prflang.index, color='orange')
ax[1].set_title('데이터 직군 - 희망 언어 TOP 10')
plt.show()
  • 데이터 직군(분석가, 사이언티스트, 데이터 엔지니어)에서 가장 많이 사용/희망하는 언어만 선별

9.5 언어별 주요 직군

python
복사
grouped_df = lang_df.groupby(['LanguageHaveWorkedWith', 'DevType'])['ResponseId'].count().reset_index(name='Count')
grouped_df_sorted = grouped_df.sort_values(['LanguageHaveWorkedWith', 'Count'], ascending=[True, False])

top3_devtypes_by_lang = grouped_df_sorted.groupby('LanguageHaveWorkedWith').head(3)
top3_devtypes_by_lang_sorted = top3_devtypes_by_lang.sort_values(['LanguageHaveWorkedWith', 'Count'], ascending=[False, True]).reset_index(drop=True)

y_positions = []
current_language = None
for idx, row in top3_devtypes_by_lang_sorted.iterrows():
    if current_language != row['LanguageHaveWorkedWith']:
        current_language = row['LanguageHaveWorkedWith']
        y_positions.append((current_language, idx))

y_labels, y_ticks = zip(*y_positions)

plt.figure(figsize=(10,10))
plt.barh(y=top3_devtypes_by_lang_sorted.index, width=top3_devtypes_by_lang_sorted['Count'])
plt.yticks(y_ticks, y_labels)
plt.title('언어별 Top 3 직군')
plt.show()

마무리

  1. 데이터 불러오기: 원본 CSV 파일에서 필요한 열만 필터링
  2. 전처리:
    • 문자열 컬럼(DevType, LanguageHaveWorkedWith 등)을 세미콜론 기준으로 분리 후 행 분할(explode)
    • 결측치 및 이상치(환율 변환 후 연봉 0, 극단적 수치 등) 처리
  3. 분석 및 시각화:
    • 개발자 분류(MainBranch)에 따른 하위 유형, 언어 사용 현황
    • 인기 언어 TOP 10, 희망 언어 TOP 10
    • 데이터 직군(DATA_DEVS)에 집중한 세부 분석
  4. 결과 활용:
    • 언어 트렌드, 직군별 특징, 나이·학력·경력에 따른 편차 등 확장 가능
    • 연봉(CompTotal)과 환율을 연동해 지역별 통계도 가능

0개의 댓글