본문 바로가기

카테고리 없음

[EDA] 카카오아레나 melon-playlist-continuation EDA 코드 분석을 통한 Numpy, Pandas 학습기록

곡 별 메타 데이터 : song_meta.json

song_meta = pd.read_json('song_meta.json', typ = 'frame')

pandas를 이용하여 json파일을 읽어들일 수 있다. typ을  frame으로 지정 해 준다. 문서에는 다음과 같이 나와있다. "Convert a JSON string to pandas object."

 

type ='frame'을 제외하면 어떻게 읽어들이는지 궁금하니 한번 시도 해 보았다.제외 하든 안하는 둘다. data frame임을 확인 할 수 있었다.문서를 보니  typ에는 {‘frame’, ‘series’}두가지가 들어갈 수 있고  default는  ‘frame’이다.

 

곡 장르 코드 데이터 :genre_gn_all.json을 읽었을때는 series로 읽었다. 근데 Frame으로 읽으면 어떻게 되는가? 안읽힌다. 왜 그러는가?자세하게 공부하고 싶다면 :pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_json.html 를 참고하여 예제를 풀어보자(이번시간에는 넘어가도록 하겠다.)

 

곡별 매핑 장르 수를 알아보기 위해서 코드를 짜보자.

# 곡 아이디(id)와 대분류 장르코드 리스트(song_gn_gnr_basket) 추출

song_gnr_map = song_meta.loc[:, ['id', 'song_gn_gnr_basket']] 

왜 굳이 loc를 썼을까? song_meta[['id', 'song_gn_gnr_basket']] <--이렇게 하면 되는데... 

loc를 이용해서 가져온 객체에 값을 할당하면 기존 df를 변경가능하다.

 

# unnest song_gn_gnr_basket
song_gnr_map_unnest = np.dstack(
    (
        np.repeat(song_gnr_map.id.values, list(map(len, song_gnr_map.song_gn_gnr_basket))), 
        np.concatenate(song_gnr_map.song_gn_gnr_basket.values)
    )
)

map(func, *iterables) --> map object

Make an iterator that computes the function using arguments from each of the iterables. Stops when the shortest iterable is exhausted.

map함수는 func을 이용하여  *iterables(여기서는 모든곡에대한 '장르 코드의 basket' 들의 집합). 의 iterator를 만들어낸다. 

이 결과 인덱스는 song_id, 원소는 'basket에 들어있는원소개수' 인 리스트가 만들어진다. 

[캡처0] 이를 이용하여 캡처 2를 만들어내는것이다.

이를 이용해서 np.repeat의 함수의 인자로 호출하면. song_gnr_map.id.values의 해당 index값에 따라 반복하여  id값을 담은 배열을 만들어 낸다. 

[캡처1] 길이가. 77989인 배열을 가지고 
[캡처2] 다음과 같은 길이 77989짜리 배열을 이용하여

 

[캡처3]길이가 802859인 넘파이 배열이 생성한다.

np.concatenate를 이용하면

이것을

[캡처4]

이렇게 변경할 수 있다.

[캡처5] 길이가 802859인 배열

 

캡처 3과 캡처 5를 이용해서 np.dstack이라는 함수를 또 호출하는데.

numpy.dstack([배열1, 배열2])를 이용하여 새로운 축으로 배열1과 배열2를 이어 붙일 수 있습니다.
numpy.stack([a, b], axis=2)과 동일한 결과를 반환합니다.

shape은((1, 802859, 2),  이와 같은 결과를 반환한다. song_id별 해당하는 장르 코드이다. 

# unnested 데이터프레임 생성 : song_gnr_map
song_gnr_map = pd.DataFrame(data = song_gnr_map_unnest[0], columns = song_gnr_map.columns)
song_gnr_map['id'] = song_gnr_map['id'].astype(str)
song_gnr_map.rename(columns = {'id' : 'song_id', 'song_gn_gnr_basket' : 'gnr_code'}, inplace = True)

# unnest 객체 제거
del song_gnr_map_unnest

 

np.stack을 이용해서 만든 객체를 위 코드를 이용하여  데이터프레임으로 변환한다. 

변환 결과는 다음과 같다. 

song_gnr_count = song_gnr_map.groupby('song_id').gnr_code.nunique().reset_index(name = 'mapping_gnr_cnt')

groupby함수는?  --> 이에  대해서 실습을 한번 하고 오자 (rfriend.tistory.com/383)

A groupby operation involves some combination of splitting the object, applying a function, and combining the results. This can be used to group large amounts of data and compute operations on these groups.

데이터를 '그룹별로 나누고',  '함수를 적용' 하고 ,  '결과를 합치는' 단계를 거친다. 
groupby 함수는 그룹별로 데이터를 집계, 요약하는 방법
 

groupt에 대한 결과는 다음과 같은데 ... 

song_id별로 group이 나누어 지고(song_id가 장르코드를 두개이상가지는 경우도 있기 때문이다.)  그 후 gnr_code원소에 접근한다. 

nunique() 함수는?

Count distinct observations over requested axis.
Return Series with number of distinct observations. Can ignore NaN values.

구분된 관측치를 count한다.
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.nunique.html

song_id값이 이상하게 변했다. 왜 그러는 것인가?

reset_index(name = 'mapping_gnr_cnt')

reset_index함수를 이요하여 인덱스를 초기화하였다. song_gnr_count에 할당한다. 

 

위의     song_gnr_count를  'mapping_gnr_cnt'열을 기준으로 그룹화하고 각 그룹의 관측치(샘플수)를 counting한다. 그후 index를 바꿔준다

이렇게 해서 대부분의 곡들이 한개의 대분류 장르와 매핑되어 있으며 전체 곡의 약 13퍼센트는 2개 이상의 대분류 장르를 가짐을 알 수 있다. (세부 장르가 아니라. 대분류 장르!

가장 많이 매핑 되는 장르는?

 1. 장르 별 곡 수 count 테이블 생성 : gnr_count

아까 구해놓았던 song_id, gnr_code가 있는 데이터 프레임을 가지고 

gnr_code별로 grouping한 후 그룹별 원소의 개수를 센 후 reset_index를 한다. 

gnr_count

중간 과정의 결과를 차례대로 보면.

 

위와 같다.

 

2. 1번 테이블과 장르 meta와 join

gnr_count = pd.merge(gnr_count, gnr_code.loc[:, ['gnr_code', 'gnr_name']], how = 'left', on = 'gnr_code')
gnr_count['gnr_code_name'] = gnr_count['gnr_code'] + ' (' + gnr_count['gnr_name'] + ')'

이제

이 테이블과 gnr_count를 left merge한다. 이는 gnr_count를 기준으로 'gnr_code'열을 이용하여 merge하겠다는것.  

결과는 위와 같다. 

매핑이 되지 않는 곡들을 제거하고 이를 내림차순으로 정리하면 다음과 같다. 

 

# 3. 매핑이 되지 않은 일부 곡들은 제거
gnr_count = gnr_count[['gnr_code_name', 'song_cnt']].dropna()

# 4. 많은 곡이 매핑된 순 기준으로 내림차순 리스트 생성
gnr_list_desc = gnr_count.sort_values('song_cnt', ascending = False).gnr_code_name

# 5. plotting
gnr_code_name_plot = sns.barplot(x = 'gnr_code_name', y = 'song_cnt', data = gnr_count, order = gnr_list_desc)
gnr_code_name_plot.set_title('장르 별 매핑된 곡 수 분포')
gnr_code_name_plot.set_xlabel('대분류 장르코드')
gnr_code_name_plot.set_ylabel('곡 수')
plt.xticks(rotation = 90)
plt.show()

이를 ploting한다. 

발매 년도 별 곡 비중은?

# 1. 곡 아이디(id)와 발매일자(issue_date) 추출
song_issue_date = song_meta[['id', 'issue_date']]
song_issue_date['issue_date'] = song_issue_date['issue_date'].astype(str)

# 2. issue_date의 앞자리 네 자리를 추출하여 발매년도(issue_year) 변수 생성
song_issue_date['issue_year'] = song_issue_date['issue_date'].str[0:4]
song_issue_date.rename(columns = {'id' : 'song_id'}, inplace = True)
song_issue_date['song_id'] = song_issue_date['song_id'].astype(str)

# 3. 1990년도~ 필터링
song_issue_date_filter = song_issue_date[song_issue_date.issue_year >= '1990']

# 4. 발매년도 별 곡 수 count 테이블 생성 : issue_year_song_cnt
issue_year_song_cnt = song_issue_date_filter.groupby('issue_year').song_id.nunique().reset_index(name = 'song_cnt')

 

# 5. plotting
issue_year_song_cnt_plot = sns.barplot(x = 'issue_year', y = 'song_cnt', data = issue_year_song_cnt, color = 'grey')
issue_year_song_cnt_plot.set_title('발매년도 별 곡 수 추이 (1990년~)')
issue_year_song_cnt_plot.set_xlabel('발매년도')
issue_year_song_cnt_plot.set_ylabel('곡 수')
plt.xticks(rotation = 50)
plt.show()

자꾸 커널이 죽는데 이유가 무엇일까?....

# 2. 1번 테이블 plylst_song_tag_map + 곡 장르 테이블 song_gnr_map join
plylst_song_tag_map = pd.merge(plylst_song_tag_map, song_gnr_map, how = 'left', left_on = 'songs', right_on = 'song_id')

이 두 친구를 songs를 기준으로 left join하는 것인데.! 계속 커널이 죽는다.