9 분 소요

3. 판다스

1) 파일을 DataFrame 객체로 로딩, 기본 API

  • csv 파일 읽어서 DataFrame 객체로 변환
import pandas as pd

titanic_df = pd.read_csv('/content/gdrive/MyDrive/titanic_train.csv')

print(type(titanic_df))     # <class 'pandas.core.frame.DataFrame'>
print('DataFrame 크기: ', titanic_df.shape)     # (891, 12)
  • titanic_df.head(3)

image

  • titanic_df.info()
    • Non_Null Count : 몇 개의 데이터가 null이 아닌지
    • Dtype : 컬럼별 데이터 타입
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890          # 전체 row 수
Data columns (total 12 columns):           # 전체 column 수
 #   Column       Non-Null Count  Dtype 
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64     # 전체 891개의 데이터중 714개만 not null
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)       # 전체 12개의 컬럼들의 타입을 요약한 것
memory usage: 83.7+ KB
  • titanic_df.describe()

image

  • titanic_df['Pclass'].value_counts()
    • Pclass 컬럼에 해당하는 값들 중 유니크한 값을 센다.
      value_counts = titanic_df['Pclass'].value_counts()    ## value_counts() : 유니크한 값을 센다
      print(value_counts)
      -----------------------------------------------------------
      3    491         # Pclass = 3인 값의 개수 : 491개
      1    216
      2    184
      Name: Pclass, dtype: int64
    
  • DataFrame 객체의 열 하나는 Series 타입으로 인식한다.

      titanic_pclass = titanic_df['Pclass']
      print(type(titanic_pclass)) # 열 하나는 Series로 인식한다
      ---------------------------------------------------
      <class 'pandas.core.series.Series'>
    

2) DataFrame과 리스트, 딕셔너리, 넘파이 ndarray 상호 변환

  • 넘파이 ndarray, 리스트 → DataFrame으로 변환하기
import numpy as np

col_name1=['col1']

list1 = [1, 2, 3]
array1 = np.array(list1)

df_list1 = pd.DataFrame(list1, columns=col_name1)
print('1차원 리스트로 만든 DataFrame:\n', df_list1)

df_array1 = pd.DataFrame(array1, columns=col_name1)
print('1차원 ndarray로 만든 DataFrame:\n', df_array1)

------------------------------------
		col1
0     1
1     2
2     3
# 3개의 컬럼명이 필요함. 
col_name2=['col1', 'col2', 'col3']

# 2행x3열 형태의 리스트와 ndarray 생성 한 뒤 이를 DataFrame으로 변환. 
list2 = [[1, 2, 3],
         [11, 12, 13]]
array2 = np.array(list2)

df_list2 = pd.DataFrame(list2, columns=col_name2)
print('2차원 리스트로 만든 DataFrame:\n', df_list2)

df_array2 = pd.DataFrame(array2, columns=col_name2)
print('2차원 ndarray로 만든 DataFrame:\n', df_array2)
--------------------------------------------------------------

		col1  col2  col3
0     1     2     3
1    11    12    13
  • Dictionary → DataFrame으로 변환하기
# Key는 컬럼명으로 매핑, Value는 리스트 형(또는 ndarray)
dict = {'col1':[1, 11], 'col2':[2, 22], 'col3':[3, 33]}
df_dict = pd.DataFrame(dict)
print('딕셔너리로 만든 DataFrame:\n', df_dict)
----------------------------------------------------
col1  col2  col3
0     1     2     3
1    11    22    33
  • DataFrame → ndarray, list, dictionary로 변환
# DataFrame을 ndarray로 변환
array3 = df_dict**.values**
print(type(array3))            # <class 'numpy.ndarray'>
print(array3)
---------------------------------------------------------------
[[ 1  2  3]
 [11 22 33]]
# DataFrame을 리스트로 변환
list3 = df_dict**.values.tolist()**
print(type(list3))                # <class 'list'>
print(list3)
---------------------------------------------------------------
[[1, 2, 3], [11, 22, 33]]
# DataFrame을 딕셔너리로 변환
dict3 = df_dict.to_dict('list')        # 딕셔너리의 값이 list 형으로 반환됨
print(type(dict3))                     # <class 'dict'>
print(dict3)
----------------------------------------------------------------
{'col1': [1, 11], 'col2': [2, 22], 'col3': [3, 33]}

3) DataFrame의 컬럼 데이터 세트 생성과 수정

# 새로운 컬럼 Age_0 추가하고 그 값은 모두 0으로 지정
titanic_df['Age_0']=0
titanic_df.head(3)

# 기존 컬럼에 0 세팅
# titanic_df['Age']=0
# titanic_df.head(3)

image

titanic_df['Age_by_10'] = titanic_df['Age']*10   
# Age_by_10이라는 컬럼 생성하고, Age 컬럼 값에 10을 곱한 값을 저장
titanic_df['Family_No'] = titanic_df['SibSp'] + titanic_df['Parch']+1
titanic_df.head(3)

image

titanic_df['Age_by_10'] = titanic_df['Age_by_10'] + 100
titanic_df.head(3)

image

4) DataFrame 데이터 삭제

  • 데이터 프레임에서 데이터의 삭제는 drop() 메서드를 이용한다.
  • drop 메서드의 원형은 다음과 같다.
    • DataFrame.drop(labels=None, axis=0, index=None, columns=None, level=None, inplace=False, errors=’raise’)
    • axis
      • axis = 0 : row 축 방향으로 drop → labels에 오는 값은 인덱스가 됨
      • axis = 1 : column 축 방향으로 drop → labels에 오는 값은 칼럼이 됨

        image

    • inplace
      • inplace=True : 원본 DataFrame이 수정됨
      • inplace=False (default) : 원본은 그대로, 반환된 DataFrame은 drop된 dataFrame이 반환됨
titanic_drop_df = titanic_df**.drop**('Age_0', axis=1 )  
# 연산하고 결과를 titanic_drop_df에 담은 것. 원래 titanic_df에는 Age_0 여전히 존재

titanic_drop_df.head(3)

image

titanic_df  # Age_0가 여전히 존재함을 확인할 수 있다.

image

  • 한번에 여러 컬럼 제거
drop_result = titanic_df.drop(['Age_0', 'Age_by_10', 'Family_No'], axis=1, inplace=True)      #titanic_df에서 바로 컬럼 제거 옵션. 결과 반환 X
print(' inplace=True 로 drop 후 반환된 값:',drop_result)   # None
titanic_df.head(3)

image

  • 행을 drop
titanic_df.drop([0,1,2], axis=0, inplace=True)
titanic_df.head(3)

image

5) Index 객체

  • index 객체 추출
indexes = titanic_df**.index**
print(indexes)  # Index 객체는 RDBMS의 Primary Key 처럼 레코드를 고유하게 식별하는 객체임.
----------------------------------------
RangeIndex(start=0, stop=891, step=1)
  • Index 객체를 실제 값 array로 변환
print(**indexes.values**)
-----------------------------------------
[  0   1   2   3   4   5    ...... 888 889 890]
  • ndarray와 유사하게 단일 값 반환 및 슬라이싱 가능
print(type(indexes.values))     # <class 'numpy.ndarray'>
print(indexes.values.shape)     # (891,)
print(indexes.values[:5])       # [0 1 2 3 4]

print(indexes[:5].values)       # [0 1 2 3 4]
print(indexes[6])               # 6
  • 한 번 만들어진 DataFrame 및 Series의 Index 객체는 함부로 변경할 수 없다.
indexes[0] = 5
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-59-2fe1c3d18d1a> in <module>
----> 1 indexes[0] = 5

/usr/local/lib/python3.9/dist-packages/pandas/core/indexes/base.py in __setitem__(self, key, value)
   5033     @final
   5034     def __setitem__(self, key, value):
-> 5035         raise TypeError("Index does not support mutable operations")
   5036 
   5037     def __getitem__(self, key):

TypeError: Index does not support mutable operations
  • Series 객체는 Index 객체를 포함하지만 Series 객체에 연산 함수를 적용할 때 Index는 연산에서 제외 됨. Index는 오직 식별용으로만 사용됨
series_fair = titanic_df['Fare']

print('Fair Series max 값:', series_fair.max())
print('Fair Series sum 값:', series_fair.sum())
print('sum() Fair Series:', sum(series_fair))
print()
print('Fair Series + 3:\n',(series_fair + 3).head(3) )

-------------------------------------------------------
Fair Series max : 512.3292
Fair Series sum : 28693.9493
sum() Fair Series: 28693.949299999967

Fair Series + 3:
 0    10.2500
1    74.2833
2    10.9250
Name: Fare, dtype: float64
  • reset_index
    • 새롭게 인덱스를 연속 숫자 형으로 할당하며, 기존 인덱스는 index라는 새로운 칼럼 명으로 추가됨

        titanic_reset_df = titanic_df.reset_index(inplace=False)
        titanic_reset_df.head()
      

      image

  • 인덱스가 연속된 int 숫자형 데이터가 아닐 경우에 다시 이를 연속 int 숫자형 데이터로 만들때 주로 사용
    • 예 : ‘Pclass’ 칼럼 Series의 value_counts()를 수행하면 ‘Pclass’ 고유 값이 식별자 인덱스 역할을 했다. 이보다는 연속 숫자형 인덱스가 고유 식별자로 더 적합해 보임. → reset_index를 이용해 인덱스를 다시 만들면 된다.
    • 단, Series에 reset_index()를 적용하면 Series가 아닌 DataFrame이 반환되니 유의해야 한다. 기존 인덱스가 칼럼으로 추가돼 칼럼이 2개가 되므로 Series가 아닌 DataFrame이 반환된다.

    • reset_index 이전
      value_counts = titanic_df['Pclass'].value_counts()
      print(value_counts) 
      ------------------
      3    491
      1    216
      2    184
      Name: Pclass, dtype: int64
        
      print('value_counts 객체 변수 타입:',type(value_counts))  # <class 'pandas.core.series.Series'>
    
    • reset_index 이후
      new_value_counts = value_counts.reset_index(inplace=False)   ####e, drop=True
      print(new_value_counts)
      -------------------------
      index  Pclass
      0      3     491
      1      1     216
      2      2     184
        
      print('new_value_counts 객체 변수 타입:',type(new_value_counts)) # <class 'pandas.core.frame.DataFrame'>
    

6) 데이터 셀렉션 및 필터링

  • 넘파이의 경우 [] 연산자 내 단일 값 추출, 슬라이싱, 팬시 인덱싱, 불린 인덱싱을 통해 데이터를 추출함
  • 판다스의 경우 ix[], iloc[], loc[] 연산자를 통해 동일한 작업을 수행한다.
  • 판다스 DataFrame 뒤에 오는 [] 안에는 칼럼 명 문자 or 인덱스로 변환 가능한 표현식이 들어 갈 수 있다.
# 단일 컬럼의 데이터 추출
titanic_df['Pclass'].head(3)    

# 여러 컬럼들의 데이터 추출
titanic_df[['Survived', 'Pclass']].head(3)    

titanic_df[0] # 오류 발생 -> 컬럼명만   있음
  • 앞에서 DataFrame의 [] 내에 숫자 값을 입력할 경우 오류가 발생한다고 했는데, 판다스의 인덱스 형태로 변환 가능한 표현식은 [] 내에 입력할 수 있다.
    • 가령 titanic_df의 처음 2개 데이터를 추출하고자 titanic_df[0:2]와 같은 슬라이싱을 이용할 수 있다. → 별로 좋은 방법은 아니다.
      titanic_df[0:2]
    
  • 불린 인덱싱 표현도 가능
titanic_df[ **titanic_df['Pclass'] == 3**].head(3)

image

  • iloc[] 연산자 : 위치기반 인덱싱 → 행과 열 값으로 integer 또는 integer 형의 스랄이싱, 팬시 리스트 값을 입력해줘야 한다 .

      data = {'Name': ['Chulmin', 'Eunkyung','Jinwoong','Soobeom'],
              'Year': [2011, 2016, 2015, 2015],
              'Gender': ['Male', 'Female', 'Male', 'Male']
             }
      data_df = pd.DataFrame(data, index=['one','two','three','four'])
    

    image

      data_df.iloc[0, 0]     # 'Chulmin'
    
  • loc[] 연산자 : 명칭 기반 인덱싱 → 행 위치에는 Dataframe index 값을, 열 위치에는 컬럼명을 입력한다.
    • 아래는 인덱스 값이 ‘one’인 행의 칼럼명이 ‘name’인 데이터를 추출하는 코드이다.
      data_df.loc['one', 'Name']      # 'Chulmin'
    
  • iloc과 loc 비교

      # 위치기반 iloc slicing
      data_df.iloc[0:1, 0]
      -----------------------
      one    Chulmin
      Name: Name, dtype: object
        
      # 명칭 기반 loc slicing 
      data_df.loc['one':'two', 'Name']    #명칭기반의 경우 명칭은 숫자형이 아닐  있으니 -1 연산이 안됨.
      ---------------------------------
      one     Chulmin
      two    Eunkyung
      Name: Name, dtype: object
    
  • 참고) reset_index() 로 새로운 숫자형 인덱스를 생성 → loc 슬라이싱

      # data_df 를 reset_index() 로 새로운 숫자형 인덱스를 생성
      data_df_reset = data_df.reset_index()
      data_df_reset = data_df_reset.rename(columns={'index':'old_index'})
        
      # index 값에 1을 더해서 1부터 시작하는 새로운 index값 생성
      data_df_reset.index = data_df_reset.index+1
      data_df_reset
    

    image

      data_df_reset.loc[1:2 , 'Name']
      --------------------------------------
      1     Chulmin
      2    Eunkyung
      Name: Name, dtype: object
    
  • 불린 인덱싱

      titanic_boolean = titanic_df[titanic_df['Age'] > 60]
      titanic_boolean 
    

    image

      titanic_df[titanic_df['Age'] > 60][['Name','Age']].head(3)  # Age가 60보다 큰 데이터 중 Name, Age 컬럼에 해당하는 데이터 프레임 반환
    
  • 불린 인덱싱을 여러 개 조합해서도 사용 가능
titanic_df[ (titanic_df['Age'] > 60) & (titanic_df['Pclass']==1) & (titanic_df['Sex']=='female')]    #### &(and), |(or), ~(not)
#### 조건을 변수에 할당하여 나중에 사용할 수도 있음
cond1 = titanic_df['Age'] > 60
cond2 = titanic_df['Pclass']==1
cond3 = titanic_df['Sex']=='female'
titanic_df[ cond1 & cond2 & cond3]

7) 정렬, Aggregation 함수, Group By 적용

  • sort_values() : 정렬
    • by=[’컬럼’] : 해당 컬럼을 기준으로 정렬 수행
    • ascending : True이면 오름차순 정렬, False이면 내림차순 정렬 (default는 True)
    • inplace : False이면 원본 데이터 프레임은 그대로 유지, 정렬된 데이터 프레임 반환, True이면 원본 데이터 프레임이 정렬됨 (default는 False)
      # Name 컬럼을 기준으로 오름차순 정렬
      titanic_sorted = titanic_df.sort_values(by=['Name'])
        
      # Pclass, Name 컬럼을 기준으로 내림차순 정렬   
      titanic_sorted = titanic_df.sort_values(by=['Pclass', 'Name'], ascending=False)
    
  • Aggregation 함수
    • DataFrame에서 min(), max(0, sum(), count(0와 같은 aggregation 함수의 적용은 RDBMS SQL의 aggregation 함수 적용과 유사하다.
    • 다만 DataFrame에서 바로 aggregation을 호출할 경우 모든 칼럼에 해당 aggregation을 적용한다.
      titanic_df.count()
      ---------------------------
      PassengerId    891
      Survived       891
      Pclass         891
      Name           891
      Sex            891
      Age            714
      SibSp          891
      Parch          891
      Ticket         891
      Fare           891
      Cabin          204
      Embarked       889
      dtype: int64
    
    • 특정 컬럼에 aggregation 함수 적용 → DataFrame에서 대상 컬럼들만 추출해 적용
      titanic_df[['Age', 'Fare']].mean()
      --------------------------------
      Age     29.699118
      Fare    32.204208
      dtype: float64
    
  • groupby() 적용
    • 입력 파라미터 by 에 칼럼을 입력하면 대상 칼럼으로 groupby 된다.
    • DataFrame에 groupby()를 호출하면 DataFrameGroupBy라는 또다른 형태의 DataFrame을 반환한다.
      titanic_groupby = titanic_df.groupby(by='Pclass')
      type(titanic_groupby)     # <class 'pandas.core.groupby.generic.DataFrameGroupBy'>
    
    • DataFrame에 groupby()를 호출해 반환한 결과에 aggregation 함수를 호출하면 groupby() 대상 컬럼을 제외한 모든 칼럼에 해당 aggregation 함수를 적용한다.
      # PClass 별 모든 컬럼들의 count
      titanic_groupby = titanic_df.groupby('Pclass').count()
      titanic_groupby
    
    • group by에 특정 칼럼만 aggregation 함수를 적용하려면 groupby로 반환된 DataFrameGroupBy 객체에 해당 컬럼을 필터링 한 뒤 aggregation 함수를 적용한다.
      titanic_groupby = titanic_df.groupby('Pclass')[['PassengerId', 'Survived']].count()
      titanic_groupby
    
  • 서로 다른 aggregation 함수를 적용하는 경우
    • SQL : select max(Age), min(Age) from titanic_table group by Pclass
titanic_df.groupby('Pclass')['Age'].agg([max, min])
  • SQL에서 select max(Age), sum(SibSp), avg(Fare) from titanic_table group by Pclass를 DataFrame groupby에서는 어떻게 처리하는가?

      agg_format={'Age':'max', 'SibSp':'sum', 'Fare':'mean'}
      titanic_df.groupby('Pclass').agg(agg_format)
    

8) 결손 데이터 처리하기

  • isna()로 결손 데이터 여부 확인

      titanic_df.isna().head(3)
    
  • 각 컬럼 별 결손 데이터 개수 확인

      titanic_df.isna( ).sum( )
      ----------------------------------------
      PassengerId      0
      Survived         0
      Pclass           0
      Name             0
      Sex              0
      Age            177
      SibSp            0
      Parch            0
      Ticket           0
      Fare             0
      Cabin          687
      Embarked         2
      dtype: int64
    
  • fillna()로 결손 데이터 대체하기

      titanic_df['Cabin'] = titanic_df['Cabin'].fillna('C000')
      titanic_df.head(3)
    
      titanic_df['Age'] = titanic_df['Age'].fillna(titanic_df['Age'].mean())
      titanic_df['Embarked'] = titanic_df['Embarked'].fillna('S')
      titanic_df.isna().sum()
      ------------------------------------------------------
      PassengerId    0
      Survived       0
      Pclass         0
      Name           0
      Sex            0
      Age            0
      SibSp          0
      Parch          0
      Ticket         0
      Fare           0
      Cabin          0
      Embarked       0
      dtype: int64
    

9) Apply lambda 식으로 데이터 가공

  • lambda : 함수의 선언과 함수 내의 처리를 한 줄의 식으로 쉽게 변환하는 식
def get_square(a):
    return a**2
get_square(3)

->  lambda_square = lambda x : x ** 2
print('3의 제곱은:',lambda_square(3))
 
  • DataFrame의 lambda
# titanic_df에서 Name_len이라는 컬럼을 생성하고 Name 컬럼의 길이를 저장해라
titanic_df['Name_len']= titanic_df['Name'].apply(lambda x : len(x))
titanic_df[['Name','Name_len']].head(3)
# titanic_df에 Child_Adult 컬럼을 생성하고 Age 컬럼의 값이 15보다 작거나 같으면 Child를, 크면 Adult를 저장해라 

titanic_df['Child_Adult'] = titanic_df['Age'].apply(lambda x : 'Child' if x <=15 else 'Adult' )
titanic_df[['Age','Child_Adult']].head(8)
titanic_df['Age_cat'] = titanic_df['Age'].apply(lambda x : 'Child' if x<=15 else ('Adult' if x <= 60 else 'Elderly'))

titanic_df['Age_cat'].value_counts()
# 나이에 따라 세분화된 분류를 수행하는 함수 생성. 
def get_category(age):
    cat = ''
    if age <= 5: cat = 'Baby'
    elif age <= 12: cat = 'Child'
    elif age <= 18: cat = 'Teenager'
    elif age <= 25: cat = 'Student'
    elif age <= 35: cat = 'Young Adult'
    elif age <= 60: cat = 'Adult'
    else : cat = 'Elderly'
    
    return cat

# lambda 식에 위에서 생성한 get_category( ) 함수를 반환값으로 지정. 
# get_category(X)는 입력값으로 ‘Age’ 컬럼 값을 받아서 해당하는 cat 반환
titanic_df['Age_cat'] = titanic_df['Age'].apply(lambda x : get_category(x))
titanic_df[['Age','Age_cat']].head()