프로젝트 정리

국비 데이터 분석 3차 _ 도메인 지식을 위한 2차 분석

01241 2023. 4. 26. 15:22

2023.04.26 - [분류 전체보기] - 국비 데이터 분석 3차 _ 1차 전처리 및 각 도메인 지식 확보

 

국비 데이터 분석 3차 _ 1차 전처리 및 각 도메인 지식 확보

우선 이전 데이터에서 dnaceablity : 얼마나 댄스적인지 energy : 얼마나 에너지적인 요소가 있는지 key : 0~12 C ~ B key loudness : -60 ~ 0 dB 평균 소리크기 mode : 0 단조 1장조 speechiness : 얼마나 말에 가까운지

kcocu.tistory.com

이전 게시글의 도메인지식 파악을 위해 knn, Logistic Regression을 돌려 도메인 지식 확보와 실제 서비스에서 사용할 컬럼을 선택하기로 했다.

 

우선 데이터를 불러왔다.

import requests
import time
import os
import urllib.request
import re
import pickle
from pprint import pprint
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn
import scipy

# 틱톡 곡 중 못 올라간 노래들 0
df_n0 = pd.read_csv('tictok2_allin_1.csv' , index_col=0)

# 틱톡 곡 중 올라간 노래들 1
df_y1 = pd.read_csv('tictok_0413.csv' , index_col=0)

df_ori = pd.concat([df_n0_s, df_y1_s],ignore_index=True)

 

데이터의 popularity_track의 분포를 확인하기 위해 그래프를 찍어봤다.

0이 툭 튀어나와 의심스러운데, 좀더 쪼개 보기 위해 bins를 늘렸다.

0이 8개이지만, 10 이하의 데이터가 엄연히 존재하고, 각 곡들의 제목, 가수를 체크하니 충분히 있을 수 있는 데이터로 판별됐다. 또, boxplot으로 체크한 결과 이상치로 보이지 않기에 그냥 가져와 사용하기로 했다.

마찬가지로 popularity_artist의 데이터를 가져왔다.

bins = 100

 

빌보드 노래를 들어보면 트랩이 많다. 다른 노래의 경우 쿵짝쿵짝 2박에 맞춰 스네어가 나오는데 트랩류는 쿵쿵짝쿵처럼 2배 느린 타이밍에 스네어가 나온다. 이를 동일하기 맞추기 위해, genres2에 rap이 들어간 노래 중 tempo가 120 이상인 곡(하프로 가는 노래)는 실제 템포의 2로 나누기로 했다.

def half_tempo_rap(x):
    if 'rap' in x['genres2']:
        if x['tempo'] >= 120:
            return x['tempo']/2
    else :
        return x['tempo']
df_ori['half_tempo_check'] = df_ori.apply(half_tempo_rap, axis=1)

우선 data의 describe()로 분포모양을 확인했다.

min에 0인 곡이 하나 있는데 이는 틱톡 인기곡 중 slience 3min이라 하여 3분간 아무 소리가 나지 않는 곡이 있기 때문이다.

이 곡은 틱톡에서 실제로 인기있고 많이 사용하는 노래이기 때문에 결측치나 이상치로 빼지 않고 사용하기로 했다.

 

spotify의 데이터 중 실제 사용할 것들 가져왔다.

data_s_2_pred = data_s_2[['score', 'danceability', 'energy', 'loudness', 'mode',
       'speechiness', 'acousticness', 'instrumentalness', 'liveness',
       'valence', 'popularity_track', 'popularity_artist', 'half_tempo_check']]

측정하기 위해 스케일링을 진행했다. 

※참고 : 실제 서비스를 예측할 때는 스케일링 없이 진행했다. 왜냐하면 spotify의 제공되는 데이터 자체가 스케일링 된 데이터이기 때문이다. 서비스시 이 과정을 진행하면 나중에 다시 원래대로 돌려야하는 현상이 있기에 나중에는 없이 진행했다.

 

일단, standard, minmax, robust 세가지로 진행했고 예측이 잘 되는 것으로 선택하려 했다.

# 스케일링
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# Standard
st_scaler = StandardScaler()
st_scaler.fit(data_s_2_pred)
st_scaled = st_scaler.transform(data_s_2_pred)
st_scaled = pd.DataFrame(st_scaled, columns=data_s_2_pred.columns)
round(st_scaled.describe(), 2)

# Robust
rb_scaler = RobustScaler()
rb_scaled = rb_scaler.fit_transform(data_s_2_pred)
rb_scaled = pd.DataFrame(rb_scaled, columns=data_s_2_pred.columns)
round(rb_scaled.describe(), 2)

# MinMax
mm_scaler = MinMaxScaler()
mm_scaled = mm_scaler.fit_transform(data_s_2_pred)
mm_scaled = pd.DataFrame(mm_scaled, columns=data_s_2_pred.columns)
round(mm_scaled.describe(), 2)

이 코드는 scaler() 함수만 가져오고 아래 새롭게 나눈 train,test를 fit해서 확인했다.

 

test, train을 8:2로 나눴다.

from sklearn.model_selection import train_test_split
X = data_s_2_pred.drop('score', axis=1)
y = data_s_2_pred['score']
X_train, X_test, y_train, y_test = \
    train_test_split(X, y,
                     test_size=0.2, random_state=4)
# MinMax 
mm_scaler.fit(X_train)
X_train_scaled = mm_scaler.transform(X_train)
X_test_scaled = mm_scaler.transform(X_test)

# Robust
rb_scaler.fit(X_train)
X_train_scaled = mm_scaler.transform(X_train)
X_test_scaled = mm_scaler.transform(X_test)

 

KNN 분류를 하기 위해 아래 코드를 작성했다.

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()
knn.fit(X_train_scaled,y_train)
pred = knn.predict(X_test_scaled)

 

정확도 예측을 위해 아래 코드를 작성했다.

from sklearn.metrics import accuracy_score
accuracy_score(y_test, pred)

KNN을 몇개로 나눌지 초매개변수를 찾기 위해 아래 코드를 작성했다.

scores=[]
for i in range(1, 21):
    knn = KNeighborsClassifier(n_neighbors=i,weights='distance')
    knn.fit(X_train_scaled,y_train)
    pred = knn.predict(X_test_scaled)
    acc = accuracy_score(y_test, pred)    
    scores.append(acc)

엘보우 기법에 따라 최적의 클러스터 개수를 7개로 정했다.

그래서 새로 학습시키고 acc를 보면

# 엘보우 기법에 따라 최적의 클러스터 개수를 7로 잡음
knn = KNeighborsClassifier(n_neighbors=7)
knn.fit(X_train_scaled,y_train)
pred = knn.predict(X_test_scaled)
accuracy_score(y_test, pred)

아래와 같이 나온다.

 

이를 시각화하려면 예측변수 2개까지만 가능하고, 이것을 돌려가면서 체크했다.

시각화하면 아래와 같다.

# 시각화를 위한 패키지
from matplotlib import pyplot as plt
from matplotlib.colors import ListedColormap
import seaborn as sns
from sklearn import neighbors, datasets
import numpy as np
 
# K-최근접이웃 분류의 시각화를 좌표평면에 하기 위해서는 예측변수를 2개까지만 사용할 수 있음
# sepal length (cm)와 sepal width (cm)의 값만 뽑아 2차 배열로 변환
pong = 10
X2 = np.array(X_test_scaled[:,pong:(pong+2)])
 
# color map 설정
cmap_light = ListedColormap(['orange','cornflowerblue'])
cmap_bold = ['darkorange', 'darkblue']

# 앞서 정확도가 가장 높았던 파라미터를 입력함
n_neighbors= 6
weights = 'distance'
knn = KNeighborsClassifier(n_neighbors=n_neighbors, weights=weights, p=2)
knn.fit(X2,y_test)
 
# 분류 경계를 색으로 구분하기 위한 작업
h = .02  # step size in the mesh
x_min, x_max = X2[:, 0].min() - 1, X2[:, 0].max() + 1
y_min, y_max = X2[:, 1].min() - 1, X2[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
 
# 그래프 사이즈 설정
plt.figure(figsize=(8, 6))
 
# 분류 경계 별 색칠하기
plt.contourf(xx, yy, Z, cmap=cmap_light)
 
# 각 훈련값의 좌표들을 그린 산점도
sns.scatterplot(x=X2[:, 0], y=X2[:, 1], hue=y_test,
                    palette=cmap_bold, alpha=1.0, edgecolor="black")
 
# 제목에 KNN 분류 파라미터와 표준 정확도 표기
plt.title(f"2-Class classification (k={n_neighbors}, {weights}), mean accuracy={accuracy_score(y_test, pred):.2f}")
plt.xlabel(data_s_2_pred.columns[pong+1])
plt.ylabel(data_s_2_pred.columns[pong+2])
 
plt.show()


여러 종합되어있는 장르들을 0, 1 로 나누어 로지스틱 회귀분류를 해봤다.

일단 테스트 해 예측할 score의 분포를 보면

def write_percent(ax, total_size):
    '''도형 객체를 순회하며 막대 상단에 비율 표시'''
    for patch in ax.patches:
        height = patch.get_height() # 도형 높이(데이터 개수)
        width = patch.get_width() # 도형 너비
        left_coord = patch.get_x() # 도형 외쪽 테두리의 X축 위치
        percent = height/total_size*100 # 타깃값 비율
        #(x,y) 자표에 텍스트 입력
        ax.text(x=left_coord + width/2.0, #x축 위치
                y=height + total_size*0.001, #y축 위치
                s=f'{percent:1.1f}%', #텍스트
                ha='center') # 가운데 정렬
plt.figure(figsize=(4,3))
ax = sns.countplot(x='score', data=data2)
write_percent(ax, len(data2)) # 비율 표시
ax.set_title('Target Distribution')

5.6:4.4 로 거의 유사하기 때문에 데이터가 균형잡혔다 생각하고 작업했다.

 

장르별로 분포를 체크하면 아래와 같고,

import matplotlib.gridspec as gridspec # 여러 그래프를 격자 배치
mpl.rc('font', size=12)
plt.figure(figsize=(10, 12))
bin_features = ['mode']
for idx, feature in enumerate(bin_features):
    ax = plt.subplot(grid[idx])
    # ax축에 target 값 분포 카우트플롯 그리기
    sns.countplot(x=feature, data=data2,
                  hue='score', 
                  palette='pastel',
                  ax=ax)
    ax.set_title(f'{feature} Distribution by Target') # 제목
    write_percent(ax, len(data2))

각 컬럼별 데이터를 체크해보면

mpl.rc('font', size=12)
grid = gridspec.GridSpec(4, 4) 
plt.figure(figsize=(20, 36))
plt.subplots_adjust(wspace=0.4, hspace=0.3) # 서브플롯 간 좌우/상하 여백
bin_features = [
    'danceability', 'energy', 'loudness',
    'speechiness', 'acousticness', 'instrumentalness', 'liveness',
    'valence', 'duration_ms', 'score',
    'popularity_track', 'popularity_artist', 'genres2',
    'half_tempo_check'
    ] 
for idx, feature in enumerate(bin_features):
    ax = plt.subplot(grid[idx])
    # ax축에 target 값 분포 카우트플롯 그리기
    sns.histplot(x=feature, data=data2,
                  hue='score',
                  palette='pastel',
                  ax=ax)
    ax.set_title(f'{feature} Distribution by Target') # 제목
    write_percent(ax, len(data2))

이런 모양이 나왔다. 우선 이대로 사용할 수 없는 genres2를 제외하고 나머지 데이터를 가지고 아래와 같이 해석하였다.

더보기

틱톡에서 빌보드에 진입한 노래 : 1 45
틱톡에서 빌보드로 진입 못한 노래가 : 0 55

1. dance : 틱톡에서 빌보드로 올라간 곡과 틱톡에서만 머무른 곡을 비교했을 때 차이가 적음.
하지만, 틱톡에 유행한 노래와 단순히 빌보드에 유행한 노래만 비교했을 때 틱톡에서 유행한 노래가 dance가 높음
_ 단,  빌보드에 올라간 모든 노래는 댄스가 0.35보다 크다.

2. energy : 틱톡에서 빌보드로 올라간 곡들은 0.45~1 사이로 0.5~8 에서 높은 정규분포를 띄고 있음.
틱톡에만 머문 곡은, 중간이 아니라 좌 우로 높게 형성되어 있음?
-> 틱톡에는 에너지는 어떤 곡이든 올라갈 수 있지만, 그중 빌보드로 올라가는 곡은 0.5~8 전후에 있는 노래가 올라간다.


3. loud는 어떤 지점을 넘기지 못하면 빌보드에 진입 못할 것이다. , _> 순수 빌보드 데이터를 대조해서, loud 최소값의 범위가 어디인지 밝혀보자. , 추가로 틱톡 노래는 loudness를 제대로 키우지 못한 곡들이 많다.

4. 1에 가까울 수록, 시 토크쇼,~~~~ 
- 0에 가까울수록 음악에 가깝다.
- 틱톡에서 말 많은 곡이 인기가 없다. (? 다른 데이터 확인 _ ***) 
~~~~ > 좀더 생각해보고 분석
- 전체적으로 0~0.1 사이의 곡이 많다. (빌보드, 틱톡 전부)
- 0.2~0.4 사이의 곡은 적지만, 이 구간에서 틱톡에 올라온 곡들 대비 빌보드로 올라갈 가능성이 크다. (이 구간이 빌보드에 올라가기 유리하다.)



5. acoustic, 틱톡에서 유행한 노래 중 어쿠스틱이 강한 노래는 빌보드에 못올라간다. 하지만, 빌보드에 현재, 어쿠스틱 음악이 많이 유행한다.
빌보드에서 유행하는 어쿠스틱 음악은 틱톡에서 유행한 노래가 아닐 가능성이 크다.

7. liveness, 라이브도 4번처럼, 0.25~0.5 구간에 있으면서 틱톡에서 유행한 노래는 빌보드에 올라갈 가능성이 크다.

8. mode, valence랑 T분포 확인하기

9. 아주 부정적이거나, 아주 긍정적인 노래가 틱톡에는 많다. 하지만 이런노래는 빌보드에 못올라간다.
고르고 분포하고, 0.3~0.8 정도의 노래가 빌보드에 잘 올라간다.

10. duration_ 는 너무 짧거나 길면 빌보드에 올라가지 못한다.

11. 인기도는 관계서 매우 크고, 이거는 아티스트랑, 트랙이랑 서로 상관관계 분석해서, 너무 높으면 하나 빼기
track_> 0인 것을 이상치 처리하고

12. 장르는 ''인 경우 ect로 넣었는데 우리가 만들 때 , 스포티파이 api로 검색시키도록 하면, ''는 빌보드에 올라갈 가능성을 낮게 보는 모델로 만들 가능성이 큼. _> ''는 데이터로 보기.

13. 템포는 아주 낮은 노래의 템포 곡들이 올라갈 가능성이 크다. _ 직접 들어서 넣기.

각 장르들을 쪼갠 후 0과 1로 나누어서 로지스틱 분류를 하기로 했다.

로지스틱 분류를 그냥해도 되지만, 다른 데이터도 분류 모델로 만들어서 어느정도 연관성을 뜨는지 확인하기로 했다.

사용할 컬럼만 먼저 가져오고

data60 = data56[['mode','score','genres2','half_tempo_check','popularity_track','loudness','speechiness','acousticness','liveness',
       'valence','duration_ms','main_instr','gender']]

참고로 main_instr과 gender은 다른 조원에게 부탁하여 레이블 받은 부분이다.

gender은 가수의 성별, main_instr는 주제악기를 찾아달라고 부탁했었다.(주제악기가 아닌, 목소리 유무를 분류했다.)

 

먼저 half_tempo를 90이하 곡,  90~120,120이상 곡으로 나누어 분류했다.

data60['half_tempo_category'] = pd.cut(data60['half_tempo_check'], bins=[50,90,120,200], right=False, labels=['slowly','middle','fast'])

더보기

# 60~90 : 힙합, 발라드 추정 1
# 90~120 : 일반 곡 추정 2
# 120~ : 달리는 곡 추정 3

모든 장르를 나누어 있으면 1 없으면 0으로 만드는 코드를 작성했다.

일반적인 방법으로는 만들 수 없어서, genres2의 장르가 all_genres안에 있으면 그에 맞는 위치에 1을 넣고 나머지는 nan으로 채우고, fillna로 채우는 식으로 작업했다. 이건 지금 만들라고 하면 apply로 깔끔하게 만들 것 같다.

for idx, i in enumerate(data60['genres2']):
    for k in i:
        for j in all_genres:
            if j == k:
                # print(j,k)
                data60.loc[idx,j] = 1
            # else :
            #     data60.loc[idx,j] = 0
data60[['pop',
       'rock', 'ect', 'edm', 'rap', 'rnb', 'house', 'dance', 'retro', 'world',
       'indie', 'syns', 'media', 'relax', 'black', 'country', 'funk',
       'future']] =\
data60[['pop',
       'rock', 'ect', 'edm', 'rap', 'rnb', 'house', 'dance', 'retro', 'world',
       'indie', 'syns', 'media', 'relax', 'black', 'country', 'funk',
       'future']].fillna(0)

아래처럼 채웠다는 말이다.

popularity의 경우는 극단적이고 정상적인 값이 있기 때문에 빈도수로 나누는 것이 맞다고 생각했다.

빈도수 기준으로 5등분 하여 분류했다

data60['popularity_track_category'] = pd.qcut(data60['popularity_track'], q=5, labels=['unknown','becomes_known','starting_to_be_popular_song','moderately_popular_song','very_popular_song'])

acousticness은 처음에 위처럼 5등분을 했다가 특징있게 분류가 되지 않아, 4등분으로 줄였다.

liveness는 사실상 잔향의 비교인데, spotifiy dev의 설명과 실제 음원을 분류했을 때 그 기준점(0.1,0.2)로 구분했다.

speechiness 역시 spotify dev 설명을 토대로 0.3,0.66 언저리로 나눴고, 0.3을 기준으로 두고 음원을 체크해본 결과 0.3보다 0.25에 더 알맞기에 bin 위치를 바꾸었다. 또 0.1 기준으로 유의미하게 변화가 나타났으므로 0.1에서  한번 더 쪼갰다.

나머지 valence와 loudness는 5등분으로 쪼개도 예쁜 모양, 분포로 쪼개졌기에 그냥 사용했다.

시간의 곡은 우선 30초 단위로 나눠봤는데, 3분 이상의 곡 개수가 유의미하게 적어서 묶었다.

data60['acousticness_category'] = pd.qcut(data60['acousticness'], q=4, labels=['edm','mix_edm','808','real_acoustic'])

# 사실상 잔향 비교임
# 0.1 이하 : 거의 순수 가상악기
# 0.1~0.2 : 라이브 악기(ex, 라이브 베이스, 라이브 기타, 라이브 피아노) 섞인 노래
# 0.2~0.5 : 실제 라이브 소리가 들어갔을 가능성이 큼 (clap, real live noise) 
# 0.5~ : 라이브 현장 노래 (함성소리, crown 등..)
data60['liveness_category'] = pd.cut(data60['liveness'],  bins=[0,0.1,0.2,0.5,1], right=True, labels=['only_virtual_inst','bit_of_live_inst','exist_live_sound','live'])

data60['speechiness_category'] = pd.cut(data60['speechiness'], bins=[0,0.1,0.25,0.66,1], right=True, labels=['normal','like_rap','rap','audio_book'])
# 0~0.1 보통의 노래
# 0.1~0.25 뮤지컬처럼 부르거나, 랩처럼 부르는 노래
# 0.25~0.66 랩으로 예측
# 0.66~ 거의 오디오 북, 노래 느낌 X
sns.histplot(x = data60['speechiness'],hue=data60['speechiness_category'])

# 체크해본 결과 3분 30초 이상의 곡들은 적어서 묶어버릴 수 있을 듯.
data60['duration_ms_category'] = pd.cut(
    data60['duration_ms'],
    bins=[0,100000,150000,200000,250000, 300000, 350000, 1000000], right=True, labels=['~1','1~1.5','1.5~2','2~2.5','2.5~3','3~3.5','3.5~'])

data60['valence_category'] = pd.qcut(data60['valence'], q=5, labels=[0,1,2,3,4])
data60['loudness_category'] = pd.qcut(data60['loudness'], q=5, labels=[0,1,2,3,4])

아래처럼 카테고리로 분류했다.

이 데이터들을 pd.get_dummies로 원 핫 인코딩하였다.

data61 = pd.get_dummies(data60,columns=[
    'half_tempo_category',  'popularity_track_category', 'loudness_category',
    'speechiness_category', 'acousticness_category', 'valence_category',
    'liveness_category', 'duration_ms_category', 'main_instr', 'gender'
], drop_first=True)

이렇게 만들어진 데이터를 test, train 데이터로 나누고

LogisticRegression에 넣어 돌렸다. 초매개변수를 찾기 위해, C, penalty 를 바꿔서 최적값을 찾았다.

그 결과 정확도 0.73, ROC 커브 0.8로 1에 가까운 괜찮은 결과가 나왔다.

test, train 차이를 봐도 0.73,0.79면 과적합은 아닐 정도?라는 생각이 들었다.

from sklearn.model_selection import train_test_split
X = data9.drop('score', axis=1)
y = data9['score']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=4)

from sklearn.linear_model import LogisticRegression
# C =0.1, 0.25, 1
# penalyt = l1, l2
# solover = liblinear로 함, saga는 양이 적어서 안먹음
model = LogisticRegression(penalty='l2', C=1, solver='liblinear')
model.fit(X_train, y_train)
pred = model.predict(X_test)

from sklearn.metrics import roc_auc_score

acc_score = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, logreg.predict_proba(X_test)[:, 1])
print(acc_score, auc)

from sklearn.metrics import accuracy_score
accuracy_score(y_test, pred)

결과를 데이터 프레임으로 바꿔서 체크해본 결과

pd.Series(model.coef_[0], index=X.columns)
results = pd.DataFrame(model.coef_[0], index=X.columns).sort_values(by=0)

어느 정도 관계가 클 것으로 예측되는 모델은 아래와 같다.

results[(results[0]>=0.5) | (results[0]<=-0.5)]

이 데이터를 해석하기 전, indie나 edm, house 등의 장르가 몇개인지 파악해야 했다.

아래 코드로 먼저 장르 수가 몇개인지 파악했다.

data_count_check = data9[['pop', 'rock', 'ect', 'edm', 'rap', 'rnb', 'house',
       'dance', 'retro', 'world', 'indie', 'syns', 'media', 'relax', 'black',
       'country', 'funk', 'future']]

check_genres_dict={}
check_genres_list=[]
for idx, i in enumerate(data_count_check.columns):
    check_genres_dict = {
        'genres_name' : i ,
        'genres_counts' : data_count_check[data_count_check.columns[idx]].value_counts().sort_index()[1]
    }
    check_genres_list.append(check_genres_dict)
    print(f'{i} 장르의 수 : {data_count_check[data_count_check.columns[idx]].value_counts().sort_index()[1]}')
더보기
pop 장르의 수 : 92
rock 장르의 수 : 19
ect 장르의 수 : 83
edm 장르의 수 : 5
rap 장르의 수 : 63
rnb 장르의 수 : 36
house 장르의 수 : 10
dance 장르의 수 : 20
retro 장르의 수 : 5
world 장르의 수 : 3
indie 장르의 수 : 15
syns 장르의 수 : 3
media 장르의 수 : 4
relax 장르의 수 : 5
black 장르의 수 : 5
country 장르의 수 : 4
funk 장르의 수 : 3
future 장르의 수 : 2

데이터가 불균형이 심하고, 한쪽으로 쏠려있기 때문에, 빈도수를 토대로 양이 적은지, 많은지를 파악해야했다.

이런 모양으로 만든 후에 중위값 이상이면 충분히 있는 데이터라고 판단하기로 했다.

len(sorted([i['genres_counts'] for i in check_genres_list]))*50/100
>> 9
sorted([i['genres_counts'] for i in check_genres_list])[9]
>> 5

5개 이상의 데이터면 충분히 장르로 예측할 수 있는 데이터로 판단했다.

그래서 아래처럼 먼저 해석했다.

더보기

ect, house, edm 장르는 틱톡 -> 빌보드 오를 가능성이 낮고,  
pop, rap 장르는 상대적으로 유리하다.  
indie 곡이 틱톡에서 인기가 있으면, 빌보드 차트에도 오를 가능성이 있다.  
tictok에서 popularity가 높은 곡들은 빌보드에 오를 가능성이 높은데,  
이 부분은 빌보드에서 높아서 틱톡에 인기가 있어진 것인지, 그 역인지 체크해야할 필요가 있다.   
exist_live_sound의 구간의 곡이 틱톡에서 인기있으면 같이 빌보드 차트에 오를 가능성이 크다.    
valence와 acousticness도 category_808 구간과 category_3 구간에서 어느 정도 있을 것으로 보인다.  
또 장조 곡보다는 단조곡이 장조곡에 비해 빌보드 진입할 가능성이 있다.  
예상과는 다르게 1분에서 1분30초의 짧은 노래가 빌보드 top100에 진입할 가능성이 있어보인다.  

 

여기에 추가로 레이블한 데이터 main_instr과 gender를 넣어서 다시 해보면

아래의 데이터가 나왔다.

더보기

ect 장르는 틱톡 -> 빌보드 오를 가능성이 낮고,   
인기도에 따라 빌보드에 오를 가능성이 어느 정도 관계가 있다.  
gender 6 : 목소리가 없으면 빌보드에 진입 가능성이 낮을 것으로 예측되며  
pop, rap 장르는 상대적으로 유리하다.    

 

추가로 main_instr3,2,1은 합쳐서 하나로 봐야하는데 main_instr가 4이면 빌보드 가능성이 높지만, 123이면 적다는 결론도 낼 수 있다. (4: 목소리가 있음, 123: 연주곡)

edm, 애매한 tempo, 길이의 곡은 가능성이 적지만

gender2:여성(틱톡에서 인기있는 노래가 여성솔로 이면)나 , 적정한 live소리가 들어가면 빌보드 가능성이 높아짐을 알 수 있다.

 

LogisticRegression를 토대로 실제 작업 전에 데이터 성분을 줄이려고 했는데, 마지막 사진을 보면 특정 장르(media, relax 등)나 카테고리에서 갯수가 적었던 것(예, speechiness_category_audio_book)은 거의 관계 없음을 볼 수 있다. 그래서 최종 서비스 모델 1을 작업 할 때는 각 컬럼은 카테고리하지 않고, 정리된 데이터를 직접 가져올 것이며, 장르를 더 축약할 필요성을 느꼈다. 

main_instr는 0,1로 쪼개고, 대신에 처음 할려했던 label을 추가하기로 했다.