본문 바로가기
데이터분석/Pandas

[Python] Pandas _ EDA _ 01 telecom

by nemonemonemo 2025. 8. 26.
import numpy as np
import pandas as pd

!gdown 1Tjuu1ODRSuQ1H0V6XVrjDRDXNDQkXpsA
'''
Downloading...
From: <https://drive.google.com/uc?id=1Tjuu1ODRSuQ1H0V6XVrjDRDXNDQkXpsA>
To: /content/telecom_churn.csv
100% 280k/280k [00:00<00:00, 86.4MB/s]
'''
# 일반적인 csv 파일!!!
path = '/content/telecom_churn.csv'
data = pd.read_csv(path, sep=",")
data.head()

  • 지역 + 코드
    • day, eve, night(사용량, 통화, 요금)+ 국제통화관련
  • 고객센터에 전화건 횟수
  • 이탈할지
data.tail() #뒤에까지 제대로 들어왔는지 체크해야함

#내가 불러들인 데이터가 숫자가 맞는지 체크
data.shape
'''
(3333, 20)
'''

len(data)
'''
3333
'''

data.info()
'''
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3333 entries, 0 to 3332
Data columns (total 20 columns):
 #   Column                  Non-Null Count  Dtype  
---  ------                  --------------  -----  
 0   State                   3333 non-null   object 
 1   Account length          3333 non-null   int64  
 2   Area code               3333 non-null   int64  
 3   International plan      3333 non-null   object 
 4   Voice mail plan         3333 non-null   object 
 5   Number vmail messages   3333 non-null   int64  
 6   Total day minutes       3333 non-null   float64
 7   Total day calls         3333 non-null   int64  
 8   Total day charge        3333 non-null   float64
 9   Total eve minutes       3333 non-null   float64
 10  Total eve calls         3333 non-null   int64  
 11  Total eve charge        3333 non-null   float64
 12  Total night minutes     3333 non-null   float64
 13  Total night calls       3333 non-null   int64  
 14  Total night charge      3333 non-null   float64
 15  Total intl minutes      3333 non-null   float64
 16  Total intl calls        3333 non-null   int64  
 17  Total intl charge       3333 non-null   float64
 18  Customer service calls  3333 non-null   int64  
 19  Churn                   3333 non-null   bool   
dtypes: bool(1), float64(8), int64(8), object(3)
memory usage: 498.1+ KB
'''

++혹시 샘플의 기준이 고객 ID, 고유값이 있다면

  • df.set_index(특정컬럼) : 가로줄 인덱스를 지정하면 됨
  • unique 보장이 되어야함 +체크 !!!

++ 컬럼명이 마음에 안 들면 변경하면 됨

  • df.rename(index = {~~~})
  • df.rename(columns = {~~~})

내가 만든 이름에 대한 정수 위치는 어디? get_loc

  • df.colums.get_loc("~~~")
  • df.colums.get_loc("~~~")
# ==> 내가 만든 이름에 대한 정수 위치는 어디? get_loc
# df.colums.get_loc("~~~")
# df.colums.get_loc("~~~")
data.columns.get_loc("Customer service calls")
'''
18
'''

cols = ["Customer service calls", "Total day minutes"]
idx = [data.columns.get_loc(col) for col in cols]
idx
'''
[18, 6]
'''
data.iloc[ 3, idx] #되긴 하는데 선호하진 않음
# --> 내가 만든 인덱스 기준으로 3에 대한 가로줄을 의미함
#     지금 이 상황에서는 내가 만든 것이 없어서 그냥 태생적인 정수와 동일하므로 먹히는 것
# 비추함
# --> 쓰다보면 0기준이 아닌 내가 만든 인덱스 정수인데 100부터 할 수도 있고, 1부터 할 수도 있음
# ==> 내가 만든 인덱스가 정수일 수 있음

해당하는 컬럼에 대한 접근

  • AM : df[컬럼명] --> 비추
  • FM
    • df.loc[:, 컬럼명]
    • df.iloc[:, -1]
data.iloc[:,-1]

Q) 위의 Churn이라는 컬럼의 이탈 여부를 나타내는 컬럼의 값을 0/1으로 명시적으로 변경하자

  • apply + lambda를 사용해서 편히 변경할 수 있음
  • lambda 함수에 조건식이 들어가야함
    • sql : select ~~~~~~~~ case when ~~~~~~~ from
    • lambda : 2분법적으로 접근
    • lambda x : True 값 if 조건식 else F값
    • → 여러개의 분할이 필요하다면?
      • sol1) 중첩을 사용해서 진행....2분할을 계속 사용하면서 : lambda x : True 값 if 조건식 else ( if 조건식2 else F2 )
      • sol2) FM으로 정식적인 함수를 만들어서 apply 태운다
data.loc[:,"Churn"].apply(lambda x : 1  if x == True else 0)

data.loc[:,"Churn"].apply(lambda x : 1  if x == True else 0).value_counts()

  • 비대칭 데이터에 대해서 항상 이슈가 생김
    • accuracy 지표는 의미 없음
      data.loc[:,"Churn"].value_counts()
      ​

data.loc[:,"Churn"].value_counts(normalize=True)

data.loc[:,"State"].value_counts()

len(data.loc[:,"State"].value_counts().index)
'''
51
'''

#참고
data.loc[:,"State"].unique()
# --> 데이터를  스캔하면서 나오는 유니크한 순서임
'''
array(['KS', 'OH', 'NJ', 'OK', 'AL', 'MA', 'MO', 'LA', 'WV', 'IN', 'RI',
       'IA', 'MT', 'NY', 'ID', 'VT', 'VA', 'TX', 'FL', 'CO', 'AZ', 'SC',
       'NE', 'WY', 'HI', 'IL', 'NH', 'GA', 'AK', 'MD', 'AR', 'WI', 'OR',
       'MI', 'DE', 'UT', 'CA', 'MN', 'SD', 'NC', 'WA', 'NM', 'NV', 'DC',
       'KY', 'ME', 'MS', 'TN', 'PA', 'CT', 'ND'], dtype=object)
'''

data.loc[:,"State"].value_counts().index
# 위와 순서가 다름
# --> value_counts는 최빈값을 중심으로 정렬이 된 유니크한 값들임
'''
Index(['WV', 'MN', 'NY', 'AL', 'OH', 'WI', 'OR', 'WY', 'VA', 'CT', 'ID', 'MI',
       'VT', 'TX', 'UT', 'IN', 'MD', 'KS', 'NJ', 'NC', 'MT', 'WA', 'CO', 'NV',
       'MS', 'MA', 'RI', 'AZ', 'MO', 'FL', 'NM', 'ME', 'ND', 'OK', 'NE', 'DE',
       'SD', 'SC', 'KY', 'IL', 'NH', 'AR', 'DC', 'GA', 'HI', 'TN', 'AK', 'LA',
       'PA', 'IA', 'CA'],
      dtype='object', name='State')
'''

주어진 데이터를 내가 원하는 기준으로 정렬해서 보자

  • Total day charge 기준을 정렬해서 볼까..
    • 내부 값을 기준으로 정렬해서 보자-> sort_values // sort_index(틀 중심)
    • 정렬 방향.... ascending = T/F
data.sort_values( by = ["Total day charge"],
                 ascending = False)

# => 요금을 많이 낸 우량 고객 중심으로 볼 수 있음

# 반대로 보면 요금을 적게 낸 고객을 어떻게 많이 내게 할까

#파이썬 sort/sorted ===> +,- 각각 정렬 기준을 지정
data.sort_values( by = ["Total day charge", "Total day calls"],
                 ascending = [False, True]) # 이건 좀 sql틱함

데이터에 대한 필터링

  • 이탈하지 않은 고객 (Churn = False) : 잘 사용하시는 고객들에 대해서
    • International plan => Yes
  • 위의 2가지 조건을 만족하는 데이터들에대해서 (샘플/가로)
  • Total intl charge를 최고값 보자
  • df.loc[가로볼 것들, 세로 볼 것들] df.loc[가로 필터링 조건들, 세로 볼 것들]
data.loc[:, "State"].apply(lambda x : x[0])

data["f-code"]= data.loc[:, "State"].apply( lambda x : x[0])
data.head()

data.loc[ :, ["State","f-code"]]

len( data.loc[:, "f-code"].value_counts().index)
'''
19
'''

ML

  • 성능 우선 주위 (kaggle 대회에서는 ok) --> 성능만 바라봄
  • 성능을 올릴 수 있는 요인, 특징이 무엇인가? 에대한 초점을 가지고 있으면
    • 변수 처리에서도 논리에, 기존 연구에 부합하면서 해야함
    • 성능이 좀 떨어지는 것은 ok

나의 목적이 무엇이냐에 따라서 달라지는 부분들이 있다.

  • ML/DL -> 왜 그렇게 성능이 나오는지 이유를 모름 (특히 DL은 더 그래)
  • 사회과학 쪽에서도 ML/DL로 접근하려는 시도들이 있음
    • 여기서 포커스는 성능보다는 해석/인과관계에 더 중점을 둠
    • 어느정도 성능이 나오긴 해야함 + SHARP
      • ex) x1을 작은 값부터 큰 값을 넣어보면서 y값이 어떻게 되는가를 시뮬레이션 해석/활용을 할 수 있는 요인 중심으로 접근을 하려고 함
      • ex) 얼굴인식 : 성능이 잘 나와야함

최근 DL은 모두 성능 중심으로 달려감

# 이렇게 재조정한 주에 대한 코드값에서 W인 것만 추려서 보자
data.loc[data.loc[:,"f-code"]=="W",:]

위의 처리 과정은 ... 명확하게 컬럼을 f-code로 만들고 w값으로 필터링 한 것

  • 이것을 만들지 않고 다이렉트로 보자
  • State 코드의 앞글자가 W인 데이터만 추려보세요... 직접 필터링으로
  • 목적 → 직접 만들기 전에 탐색으로 활용하기 위한 코드
    • data.loc[불리언인덱싱 -> 벡터연산..(array, series, df),:]
data.loc[:,"State"].apply(lambda x : x[0])

# 기존의 필터링은 주어진 값을 기반으로 필터링 조건을 활용한 경우
# Churn ==True, International plan =="Yes"

# 이건 주어진 값을 내가 원하는 대로 변경해서.. 필터링 조건으로 활용해보자
# ==> 불리언 인덱싱 + lambda + apply
data.loc[data.loc[:,"State"].apply(lambda x : x[0])=="W",:]

seaborn 그래프 스타일 파악

seaborn 스타일 : 그릴 대상 df을 기준으로 지정하는 방식

  • 파라미터
    • data : 내가 그림에 사용할 원천 DF을 지정,,,
    • x : 가로에 세팅할 data 컬럼명
    • y : 세로에 세팅할 data 컬러명
    • hue : 구별해서 그려줄 때.…
    • 참고) pandas의 pivot_table과 유사함
      • 가로:index(x), 세로 values(y)
      • 세로를 구별하면서 columns(hue)
1
import seaborn as sns
data.loc[:,"Total intl calls"]

sns.boxplot(data = data, y = "Total intl calls")

sns.violinplot( data = data, y ="Total intl calls")

# 목적 : 서로 다른 그래프들을 하나의 판에 모아서 그리자!!!
import matplotlib.pyplot as plt
# 1) 전체 판
fig, axes = plt.subplots(nrows =1, ncols = 2,sharey=True)

# 1) 전체 판
fig, axes = plt.subplots(nrows =1, ncols = 2,sharey=True)

#2) 구체적으로 그릴 영역에 대해서 지정하고 그리면 됨
# 왼쪽 axes[0] --> boxplot
# 오른쪽 axes[1] --> violin
sns.boxplot(data = data, y ="Total intl calls", ax = axes[0])
sns.violinplot(data = data, y ="Total intl calls", ax = axes[1])
# ++ 기타 꾸밈에 대한 처리 -> set_xlabel, set_ylabel, set_title 

파이썬 계열에서 그래프를 다룰 때 기본이 되는 패키지 → matplotlib

  • matplotlib이어서 기본적인 기능/동작은 파악을 해야함
  • 그래야 그리기 용이함

'데이터분석 > Pandas' 카테고리의 다른 글

[Python] Pandas _ EDA _ 03 gpt  (1) 2025.08.27
[Python] Pandas _ EDA _ 02 titanic  (2) 2025.08.27
[Python] Pandas 15 _ curl  (1) 2025.08.25
[Python] Pandas 14 _ groupby  (3) 2025.08.25
[Python] Pandas 13 _ pivot  (1) 2025.08.25