DataFrame, Series의 정렬 - sort_values()
sort_values() 는 RDBMS SQL의 order by 키워드와 유사하다.
주요 입력 파라미터는 by, ascending, inplace이다.
import pandas as pd
titanic_df = pd.read_csv('titanic_train.csv')
titanic_sorted = titanic_df.sort_values(by=['Name'])
titanic_sorted.head(3)
output
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
845 | 846 | 0 | 3 | Abbing, Mr. Anthony | male | 42.0 | 0 | 0 | C.A. 5547 | 7.55 | NaN | S |
746 | 747 | 0 | 3 | Abbott, Mr. Rossmore Edward | male | 16.0 | 1 | 1 | C.A. 2673 | 20.25 | NaN | S |
279 | 280 | 1 | 3 | Abbott, Mrs. Stanton (Rosa Hunt) | female | 35.0 | 1 | 1 | C.A. 2673 | 20.25 | NaN | S |
여러 개의 칼럼으로 정렬하려면, by에 리스트 형식으로 입력하면 된다.
titanic_sorted = titanic_df.sort_values(by=['Pclass', 'Name'], ascending=False)
titanic_sorted.head(3)
output
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
868 | 869 | 0 | 3 | van Melkebeke, Mr. Philemon | male | NaN | 0 | 0 | 345777 | 9.5 | NaN | S |
153 | 154 | 0 | 3 | van Billiard, Mr. Austin Blyler | male | 40.5 | 0 | 2 | A/5. 851 | 14.5 | NaN | S |
282 | 283 | 0 | 3 | de Pelsmaeker, Mr. Alfons | male | 16.0 | 0 | 0 | 345778 | 9.5 | NaN | S |
Aggregation 함수 사용
DataFrame에서 min(), max(), sum(), count() 와 같은 aggregation 함수의 적용은 RDBMS SQL의 aggregation 함수 적용과 유사하다. 다만 DataFrame에서 aggregation을 호출할 경우 모든 칼럼에 해당 aggregation 을 적용하게 된다.
titanic_df.count()
output
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 함수를 적용하려면 대상 칼럼만 추출하여 적용하면 된다.
titanic_df[['Age', 'Fare']].mean(axis=1)
output
0 14.62500
1 54.64165
2 16.96250
3 44.05000
4 21.52500
...
886 20.00000
887 24.50000
888 23.45000
889 28.00000
890 19.87500
Length: 891, dtype: float64
groupby() 적용
DataFrame에 groupby() 를 호출하면 DataFrameGroupBy라는 또 다른 형태의 DataFrame을 반환한다.
titanic_groupby = titanic_df.groupby(by='Pclass')
print(type(titanic_groupby))
print(titanic_groupby)
output
<class 'pandas.core.groupby.generic.DataFrameGroupBy'>
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000020495A77088>
groupby() 를 호출해 반환된 결과에 aggregation 함수를 호출하면 groupby() 대상 칼럼을 제외한 모든 칼럼에 해당 aggregation 함수를 적용한다.
titanic_groupby = titanic_df.groupby('Pclass').count()
titanic_groupby
output
PassengerId | Survived | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|
Pclass | |||||||||||
1 | 216 | 216 | 216 | 216 | 186 | 216 | 216 | 216 | 216 | 176 | 214 |
2 | 184 | 184 | 184 | 184 | 173 | 184 | 184 | 184 | 184 | 16 | 184 |
3 | 491 | 491 | 491 | 491 | 355 | 491 | 491 | 491 | 491 | 12 | 491 |
특정 칼럼에만 aggregation 함수를 적용하려면, groupby() 로 반환된 DataFrameGroupBy 객체에 해당 칼럼을 필터링한 뒤 aggregation 함수를 적용한다.
titanic_groupby = titanic_df.groupby(by='Pclass')[['PassengerId', 'Survived']].count()
titanic_groupby
output
PassengerId | Survived | |
---|---|---|
Pclass | ||
1 | 216 | 216 |
2 | 184 | 184 |
3 | 491 | 491 |
서로 다른 aggregation 함수를 적용할 경우에는 DataFrameGroupBy 객체의 agg() 내에 여러 개의 함수명을 인자로 입력하면 된다.
titanic_df.groupby('Pclass')['Age'].agg([max, min])
output
max | min | |
---|---|---|
Pclass | ||
1 | 80.0 | 0.92 |
2 | 70.0 | 0.67 |
3 | 74.0 | 0.42 |
이렇게 groupby() 를 이용해 API 기반으로 처리하다보니, SQL의 group by 보다 유연성이 떨어진다.
예컨대 여러 개의 칼럼이 서로 다른 aggregation 함수를 호출하기에는 좀 복잡하다.
이를 처리하기 위해서는 agg() 안에 딕셔너리 형태로 칼럼과 함수를 입력해야 한다.
agg_format={'Age':'max', 'SibSp':'sum', 'Fare':'mean'}
titanic_df.groupby('Pclass').agg(agg_format)
output
Age | SibSp | Fare | |
---|---|---|---|
Pclass | |||
1 | 80.0 | 90 | 84.154687 |
2 | 70.0 | 74 | 20.662183 |
3 | 74.0 | 302 | 13.675550 |
판다스는 결손 데이터(Missing Data)를 처리하는 API를 제공한다.
기본적으로 NaN 값, 즉 결손 데이터는 머신러닝 알고리즘에서 처리되지 않는다. 또한, 평균, 총합 등의 함수 연산에서도 제외된다.
isna() 를 이용해 NaN 인지 확인하고, fillna() 를 이용해 다른 값으로 대체 가능하다
isna() 로 결손 데이터 여부 확인
isna() 를 이용하면 모든 칼럼의 값이 NaN 인지 아닌지를 True나 False로 알려준다
titanic_df.isna().head(3)
output
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | False | False | False | False | False | False | False | False | False | False | True | False |
1 | False | False | False | False | False | False | False | False | False | False | False | False |
2 | False | False | False | False | False | False | False | False | False | False | True | False |
이 결과에 sum() 함수를 추가하면 전체 결손 데이터의 개수를 구할 수 있다(내부적으로 True는 1, False는 0으로 변환된다).
titanic_df.isna( ).sum( )
output
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() 로 결손 데이터 대체하기
'Cabin'칼럼의 NaN 값을 'C000' 으로 대체해보자
titanic_df['Cabin'] = titanic_df['Cabin'].fillna('C000')
titanic_df.head(3)
output
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | C000 | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | C000 | S |
이때 반환값을 원본에 다시 대입하거나, inplace=True 파라미터를 추가해야 원본 데이터 값이 변경된다.
'Age' 칼럼의 NaN 값을 평균 나이로, 'Embarked' 칼럼의 NaN 값을 'S'로 대체해 모든 결손데이터를 처리해보자.
titanic_df['Age'] = titanic_df['Age'].fillna(titanic_df['Age'].mean())
titanic_df['Embarked'] = titanic_df['Embarked'].fillna('S')
titanic_df.isna().sum()
output
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
판다스는 apply 함수에 lambda 식을 결합해 DataFrame이나 Series의 레코드별로 데이터를 가공하는 기능을 제공한다.
먼저 lambda 식에 대해 알아보자.
def get_square(a):
return a**2
print('3의 제곱은:',get_square(3))
output
3의 제곱은: 9
위와 같은 함수를 lambda 식으로 표현해보자.
lambda_square = lambda x : x ** 2
print('3의 제곱은:',lambda_square(3))
3의 제곱은: 9
lambda x : x ** 2 에서 ':'로 입력 인자와 반환될 값의 계산식을 분리한다.
lambda 식을 사용할 때 여러 개의 값을 입력인자로 사용해야 할 경우에는 보통 map() 함수를 결합해서 사용한다.
a=[1,2,3]
squares = map(lambda x : x**2, a)
list(squares)
output
[1, 4, 9]
이제 이 lambda 식을 DataFrame의 apply에 적용해보자.
titanic_df['Name_len']= titanic_df['Name'].apply(lambda x : len(x))
titanic_df[['Name','Name_len']].head(3)
output
Name | Name_len | |
---|---|---|
0 | Braund, Mr. Owen Harris | 23 |
1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | 51 |
2 | Heikkinen, Miss. Laina | 22 |
조금 더 복잡하게 if else 절을 사용해보자.
나이가 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(10)
output
Age | Child_Adult | |
---|---|---|
0 | 22.000000 | Adult |
1 | 38.000000 | Adult |
2 | 26.000000 | Adult |
3 | 35.000000 | Adult |
4 | 35.000000 | Adult |
5 | 29.699118 | Adult |
6 | 54.000000 | Adult |
7 | 2.000000 | Child |
8 | 27.000000 | Adult |
9 | 14.000000 | Child |
주의할 점은 ':' 기호의 오른편에 반환값이 있어야하기 때문에, if 식보다 반환값을 먼저 적어야 한다. else 식은 else 식 뒤에 반환값이 오면 된다.
else if는 지원하지 않기에, else에 ()를 만들어 다시 if else를 적용해야 한다.
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()
output
Adult 786
Child 83
Elderly 22
Name: Age_cat, dtype: int64
else를 괄호안에 계속 중첩해서 쓰는 것은 부담스럽다. 이때는 함수를 만드는 게 더 낫다.
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
titanic_df['Age_cat'] = titanic_df['Age'].apply(lambda x : get_category(x))
titanic_df[['Age','Age_cat']].head()
output
Age | Age_cat | |
---|---|---|
0 | 22.0 | Student |
1 | 38.0 | Adult |
2 | 26.0 | Young Adult |
3 | 35.0 | Young Adult |
4 | 35.0 | Young Adult |