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개의 댓글