index Review # Brand Variety Style Country Stars Top Ten
0 512 2068 Maggi Fusian Special Edition Ow... Ow... Spicy Cow M... Pack Australia 5.00 NaN
1 251 2329 Patanjali Atta Noodles Jhatpat Banao Befikr Khao Pack India 5.00 NaN
2 11 2569 Yamachan Yokohama Tonkotsu Shoyu Pack USA 5.00 NaN
3 380 2200 Mr. Lee's Noodles Shaolin Monk Vegetables Cup UK 5.00 NaN
4 10 2570 Tao Kae Noi Creamy tom Yum Kung Flavour Pack Thailand 5.00 NaN
5 65 2515 Uni-President Man Han Feast Spicy Beef Flavor Instant Noodles Bowl Taiwan 5.00 NaN
6 30 2550 Samyang Paegaejang Ramen Pack South Korea 5.00 NaN
7 22 2558 KOKA Creamy Soup With Crushed Noodles Hot & Sour Fi... Cup Singapore 5.00 NaN
8 883 1697 The Kitchen Food Instant Kampua Dark Soy Sauce Pack Sarawak 5.00 NaN
9 2033 547 Lucky Me! Pancit Canton Sweet Spicy Pack Philippines 5.00 NaN
Review # Brand Variety Style Country Stars Top Ten
97 2483 Paldo Bul Jjamppong Bowl South Korea 5.0 NaN
256 2324 Paldo Bul Jjajangmyeon Pack South Korea 5.0 NaN
346 2234 Paldo Bibim Men Bowl South Korea 5.0 NaN
360 2220 Paldo Budae Jjigae Pack South Korea 5.0 NaN
826 1754 Paldo King Bowl Super Spicy Pan Stirfried Noodle Bowl South Korea 5.0 NaN
903 1677 Paldo Raobokki Noodle (Export Version) Pack South Korea 5.0 NaN
1005 1575 Paldo Jjajangmen Chajang Noodle King Bowl Bowl South Korea 5.0 NaN
1057 1523 Paldo Jjamppong Seafood Noodle King Bowl Bowl South Korea 5.0 NaN
1166 1414 Paldo Cheese Ramyun (for US market) Pack South Korea 5.0 NaN
1266 1314 Paldo Korean Traditional Beef Gomtangmen Pack South Korea 5.0 NaN
1397 1183 Paldo Cheese Noodle Pack South Korea 5.0 2014 #6
1648 932 Paldo Namja Ramen (USA version) Pack South Korea 5.0 NaN
1754 826 Paldo Namja Pack South Korea 5.0 NaN
1756 824 Paldo Bibim Men Cucumber Pack South Korea 5.0 NaN
1757 823 Paldo Kokomen Spicy Chicken Pack South Korea 5.0 2013 #9
1906 674 Paldo Kko Kko Myun Pack South Korea 5.0 NaN
어... 나도 꼬꼬면 참 좋아해... 좋아하는데... 이정도로 월클일 줄 몰랐어... 비빔면은 나는 매워서 못먹지만 솔직히 월클일만 했음.
Review # Brand Variety Style Country Stars Top Ten
47 2533 Nongshim Shin Ramyun Black Pack South Korea 5.0 NaN
419 2161 Nongshim Chal Bibim Myun Pack South Korea 5.0 NaN
486 2094 Nongshim Champong Noodle Soup Spicy Seafood Flavor Pack South Korea 5.0 NaN
753 1827 Nongshim Zha Wang ((Jjawang) Noodles With Chajang Sauce Pack South Korea 5.0 NaN
979 1601 Nongshim Jinjja Jinjja (New) Pack South Korea 5.0 NaN
1272 1308 Nongshim Soon Veggie Noodle Soup Pack South Korea 5.0 2014 #9
1475 1105 Nongshim Doong Ji Authentic Korean Cold Noodles With Ch... Tray South Korea 5.0 NaN
1829 751 Nongshim Shin Ramyun Black Onion Cup South Korea 5.0 NaN
1835 745 Nongshim Jinjja Jinjja Pack South Korea 5.0 NaN
그... 둥지냉며어어어어어언이요... 조낸 비싸요... 조낸 비싼데 조낸 간단해... 우리가 모밀이나 비빔면은 라면류가 많지만 냉면은 쟤 하나거든요? 아 그래서 비싸게 받는건가... 아무튼 이게 냉면인데 걍 라면 끓여먹듯 끓이면 되고 국물도 물타면 땡입니다. 조낸 비싼거 빼면 다 좋음. 집에 김치 있어요? 백김치건 동치미건 말아잡수면 최고임.
Review # Brand Variety Style Country Stars Top Ten
30 2550 Samyang Paegaejang Ramen Pack South Korea 5.0 NaN
69 2511 Samyang Samyang Ramen Classic Edition Bowl South Korea 5.0 NaN
214 2366 Samyang Buldak Bokkeummyun Snack Pack South Korea 5.0 NaN
215 2365 Samyang Stew Buldak Bokkeumtangmyun Pack South Korea 5.0 NaN
298 2282 Samyang Gold Jjamppong Fried Noodle Pack South Korea 5.0 NaN
606 1974 Samyang Cheese Curry Ramyun Pack South Korea 5.0 NaN
1280 1300 Samyang Red Nagasaki Jjampong Pack South Korea 5.0 NaN
1382 1198 Samyang Maesaengyitangmyun Baked Noodle Pack South Korea 5.0 2014 #5
1551 1029 Samyang Nagasaki Crab Jjampong Pack South Korea 5.0 NaN
Review # Brand Variety Style Country Stars Top Ten
1272 1308 Nongshim Soon Veggie Noodle Soup Pack South Korea 5.00 2014 #9
1382 1198 Samyang Maesaengyitangmyun Baked Noodle Pack South Korea 5.00 2014 #5
1397 1183 Paldo Cheese Noodle Pack South Korea 5.00 2014 #6
1757 823 Paldo Kokomen Spicy Chicken Pack South Korea 5.00 2013 #9
2002 578 Nongshim Shin Ramyun Black Pack South Korea 4.75 2012 #7
cup_top10 = ramen_cup.copy()
pack_top10 = ramen_pack.copy()
cup_top10['Is Top Ten'] = cup_top10['Top Ten'].apply(lambda x: 'Top Ten' if pd.notnull(x) and x != '' else 'None')
pack_top10['Is Top Ten'] = pack_top10['Top Ten'].apply(lambda x: 'Top Ten' if pd.notnull(x) and x != '' else 'None')
# 컵라면 중 Top Ten에 선정된 녀석들만 추출
pack_top10_winners = pack_top10.query('`Is Top Ten` == "Top Ten"')
# 그 안에서 국가별 비중 확인
pack_top10_korea = pack_top10_winners['Country'].value_counts()
print("Top 10에 선정된 봉지라면들의 국적 분포:")
print(pack_top10_korea)
# 컵라면 중 Top Ten에 선정된 제품의 브랜드와 제품명 출력
elite_pack = pack_top10.query('`Is Top Ten` == "Top Ten" and Country == "South Korea"')[['Brand', 'Variety', 'Country', 'Top Ten']]
print("--- 봉지라면계의 전설(들) ---")
print(elite_pack)
--- 봉지라면계의 전설(들) ---
Brand Variety Country Top Ten
1272 Nongshim Soon Veggie Noodle Soup South Korea 2014 #9
1382 Samyang Maesaengyitangmyun Baked Noodle South Korea 2014 #5
1397 Paldo Cheese Noodle South Korea 2014 #6
1757 Paldo Kokomen Spicy Chicken South Korea 2013 #9
2002 Nongshim Shin Ramyun Black South Korea 2012 #7
# 1. 콤마(,)로 나누고(split), 양쪽 공백을 제거(strip)한 리스트의 길이를 측정
meta_df['Platform_classification'] = meta_df['Platform'].apply(
lambda x: 'Singleplatform' if len(str(x).split(',')) == 1 else 'Multiplatform'
)
# 결과 확인
print(meta_df['Platform_classification'].value_counts())
멀티플랫폼인 경우 대부분 PC도 포함한다. 그리고 플랫폼이 하나인 경우에는 PC판만 내거나, 콘솔 독점이거나… 요즘 근데 닌텐도 말고는 독점으로 잘 안 내지 않나?
sns.countplot(data=meta_df, x='Platform_classification', hue='PC_included', palette="icefire")
plt.legend(['PC판 미발매', 'PC판 발매'])
plt.xlabel('단일 플랫폼 여부 및 PC판 발매 여부')
plt.ylabel('발매 게임 수')
plt.title('단일 플랫폼 여부 및 PC판 발매여부에 따른 게임 수', fontsize=20)
plt.show()
요즘 스팀이나 에픽에서도 뭐 많이 할 수 있으니께... 게임패드요? PC에 연결 되는것도 많다. 가끔 스팀 맞춤 대기열 보면 원래 모바일게임으로 나왔던 게 갑자기 스팀으로 나오는 경우도 있다. 명조도 스팀으로 나왔고.
년도별로는 이렇게 된다. 멀티플랫폼인 게임은 23년도에, 싱글플랫폼인 게임은 24년도에 많이 발매됐다.
닌텐도도 멀티플랫폼이 있나요?
결론부터 말하자면 있긴 있다. 근데 자사 퍼스트파티나 세컨드파티가 멀티플랫폼으로 나오는 건 아니고... 얘네들 그런쪽으로 문호개방 안함... 대신 다른 게임사에서 저희 스위치로도 게임 내고 싶습니다! 하면 그건 웰컴이다. 가끔 모바일로 나오는건 중국산 짝퉁게임이거나 공식에서 내는 외전이거나.
# 쿼리 조건: SARS-CoV-2 (코로나19), Spike 단백질 위주로 털어보기
# 2025년 최신 데이터 + 사람 숙주 조건
query = "SARS-CoV-2 AND S[Gene Name] AND 2025[PDAT] AND Homo sapiens[Host]"
# 1. ID 리스트 가져오기
handle = Entrez.esearch(db="nucleotide", term=query, retmax=300)
record = Entrez.read(handle)
id_list = record["IdList"]
handle.close()
# 2. 실제 서열 데이터 가져오기 (FASTA 형식)
fetch_handle = Entrez.efetch(db="nucleotide", id=id_list, rettype="fasta", retmode="text")
sequences = list(SeqIO.parse(fetch_handle, "fasta"))
fetch_handle.close()
# 3. 저장
with open("influenza_h3n2.fasta", "w") as f:
SeqIO.write(sequences, f, "fasta")
print(f"성공적으로 {len(sequences)}개의 서열을 가져왔습니다.")
print("----------")
for record in sequences[:3]:
print(f"ID: {record.id}")
print(f"Description: {record.description}")
print(f"Length: {len(record.seq)} bp\n")
...근데 저걸 꼭 2025년만 가져오는 이유가 있는거니 제미나이야...?
# 콤퓨타에 저-장
vir_sequence = []
for i, id in enumerate(id_list):
print(f"Downloading sequence {i+1}/{len(id_list)}: {id}")
handle = Entrez.efetch(db="nucleotide", id=id, rettype="fasta", retmode="text")
record = SeqIO.read(handle, "fasta")
# record에 id와 seq가 다 들어가야되더라... (안되면 오류남 봤음)
vir_sequence.append(record)
# 파일로 저장
SeqIO.write(vir_sequence, "hantavirus_sequence.fasta", "fasta")
print('Done!')
# 시퀀스 길이 체크
for rec in vir_sequence:
print(f"ID: {rec.id} | Length: {len(rec.seq)}")
이게 그… 체크해봤더니 완전 잡탕찌개인거예요… 뭔 닥터 스트레인지 왔다감도 아니고 대혼돈의 유니버스여 게놈이… 저게 전체 데이터는 한 다섯자리 되고, 중간중간 3~4000bp정도 되는게 바이러스 스파이크(그 겉면에 삐죽삐죽한 작대기 있음)인데, 저걸 저대로 MSA하면 일단 똥때리고 밥먹고 자다 인나서 다음날이 됐는데도 MSA가 안 끝났을 확률이 99.9%입니다. 그리고 우리 스파이크만 할거임.
# 가져온 sequences 리스트에서 길이로 필터링
spike_only = [rec for rec in sequences if 3000 <= len(rec.seq) <= 4500]
# 필터링된 결과 저장
with open("sars_cov2_spike_clean.fasta", "w") as f:
SeqIO.write(spike_only, f, "fasta")
print(f"전체 {len(sequences)}개 중 진짜 스파이크만 {len(spike_only)}개 골라냈습니다.")
그래서 어떻게 해요? 아 스파이크만 거르셔야죠.
MSA
print('MSA start... ')
# MSA 분석 시-작
try:
result = subprocess.run([muscle_exe, "-align", "sars_cov2_spike_clean.fasta", "-output", "sars_cov2_spike_align.fasta"], check=True, capture_output=True, text=True)
print("Completed. ")
except subprocess.CalledProcessError as e:
print(f"MSA failed: {e}")
finally:
alignment = AlignIO.read("influenza_h3n2_muscle_aligned.fasta", "fasta")
# 밥 먹고 오면 끝나있겠는데...?
보통 돌려놓고 똥때리고 오면 끝나있습니다. 담배는 안되냐고요? 뭐 알아서 피시든지... 나한테 냄새만 안 오게 하십쇼.
print("====== MSA Result ======")
alignment = AlignIO.read("sars_cov2_spike_align.fasta", "fasta") # FASTA 니네 확장자가 몇개냐...
for record in alignment:
print(f"{record.id[:10]:<15} : {record.seq[:100]}")
코드는 네이버에서 짤릴까봐 뺐습니다. 이것도 깃헙가서 보세요. 근데 트리 코드는 딱히 바뀐게 없음…
스피어맨 상관계수
그 우리 인플루엔자 할 때도 트리 관련해서 맨 휘트니 안했죠? 그때 내가 걔들은 같은 아종에서 바뀌는거라 맨 휘트니 하면 결과 이상하게 나온다고 했잖아요. 근데 얘네도 비슷해요 이유는. 스파이크 단백질이 어어하면 숙주 찾아서 입장하기도 전에 뽀사지거든… 그래서 보존구역이 많은거고, 그래서 맨 휘트니를 못합니다.
rho, p = spearmanr(tree_distances, seq_identities)
print(f'Rho: {rho:.4f}')
print(f'P-value: {p:.4e}')
그 우리 이제 할만큼 하셨으니까... 전처리는 다 안올릴게여... 내가 귀찮아서 이러는거 맞음. 내가 네이버 티스토리 미디움에 동시에 올리는데 그러면 복붙을 몇 번 하는겁니까...
배경설명
이번에 가져온 데이터베이스의 주제가 Kinase Inhibitor입니다. 이게 뭔데요? 를 설명하려면 kinase와 inhibitor를 다 설명해야 하는데… 아…
Kinase는 뭐에 인산기를 붙이는 효소입니다. 인산기는 PO4(3-)고, 그걸 어디다가 붙이는 놈들이 다 카이네이스예요. 인산기를 어디다가 붙이는가는 효바효(효소 바이 효소)지만 어쨌든 인산기를 갖다 붙입니다. 보통 ATP에서 떼다가 붙이긴 합니다만.
자, 그럼 인히비터에 대한 얘기를 해봅시다. 우리 몸의 모든 것은 신호체계를 기반으로 돌아갑니다. 뭘 대사하는 과정, 숨쉬는것, 세포분열까지 하나한 신호체계가 관여하고 있고, 그 신호체계에 끼어드는 놈이 kinase다 이겁니다. 당연하게도 이 신호체계는 멈춰야 할 때 멈추고, 돌아야 할 때 도는 게 정상입니다. 적절하게 엑셀/브레이크가 제 역할을 해 줘야 한다 이거죠.
그런데 이 엑셀과 브레이크가 선천적으로 없거나(유전병이라던가...), 후천적으로 없어진다면(돌연변이 누적)? 네. 질병입니다. 암도 여러 돌연변이가 누적되어서 엑셀, 브레이크가 다 맛이 가면 생기게 되는겁니다. 그럼 인히비터는 여기서 뭘 하는데요? 엇나간 엑셀, 엇나간 브레이크가 신호체계 개발살내는 걸 방해하는겁니다.
kinase가 다양한 곳에서 다양한 역할을 하기 때문에, 그 kinase를 타겟으로 하는 약물들도 다양합니다.
# boxplot
sns.boxplot(df, x = 'Status', y = 'Molecular Weight', hue = 'Status')
plt.yscale('log')
plt.title('Molecular Weight by Status')
plt.show()
분자량 6000 넘는거 확인해봤더니(수상하게 멀리 튀어있음) 올리고뉴클레오타이드더라... 하나는 클리니컬, 하나는 페일드인데 일단 올리고뉴클레오타이드는 원래 뚠뚠합니다. 인별 릴스 보면 뭐 딸기에서 DNA 뽑기 이런거 하죠? 그게 일단 전체 조직에서 뽑는거기도 하고 DNA가 세포 안에 실패로 감고 감고 감아서 염색체로 짱박아야 할 정도로 뚠뚠함.
쟤는 VEGF라는 애가 타겟인데, 타겟인 애가 kinase가 아니고 kinase를 작동하기 위한 열쇠다. 그러니까 자동차로 비유하자면 자동차에 차 키를 못 꽂게 방해하는겁니다.
근데 왜 실패했냐고? DNA도 뱃속으로 들어가면 걍 영양원입니다 여러분. 그리고 쟤가 siRNA인데... 그 우리 RNA 뽑을 때 DEPC-증류수 쓰는게 RNase때문입니다. 실험에 써야되는데 걔가 다짤라... 걔를 무력화할 수단 중 하나가 DEPC라서 물에 그걸 타서 쓰는건데... 약 하나 투여하자고 그걸 같이 넣을 순 없잖습니까... GHS에 느낌표 떴다고... 이렇게 말하면 아니! RNA 백신은 그럼 허구냐! 하실 수 있는데, 제약사들도 똥멍청이가 아닙니다. 석박들이 모여서 머리를 맞대고 이거 분해 안되고 배달되게 할 방법 없을까? 해서 방법을 강구했죠. 리포좀이나 인지질같은 걸로 싸서 효소가 접근을 못 하게 하거나, 아예 퀵으로 목적지까지 쏴버리거나, backbone(그 사다리에 양 옆에 1자로 서있는거)을 바꾸거나.
누누이 말하는거지만 어노바(분산분석)는 얘네들 중 뭔가 다른데? 만 알려주는거고 튜키까지 해야 뭐가 구체적으로 다른지를 알려줍니다. 여기서는 승인됨 그룹이 다른 두 그룹과 다르다는 걸 튜키가 알려준거임.
AlogP의 경우 임상 단계와 상관 없이 6을 넘는 애들이랑 0보다 작은 애들이 있었는데, 일단 6보다 큰 애들은 그만큼 기름지시다는 의미고 0보다 작은 애들은 이건 뭐 물이야 뭐야라고 보시면 된다. 리핀스키 룰에서는 0~5까지로 지정하는데 왜 그러냐면 세포막은 인지질이지만 우리 몸은 물이거든...
AlogP가 규격 외인 약물들이 승인 단계에서 가장 많이 보이는 게 의외였음. 일단 임상중에서도 혼자 3상인 SONROTOCLAX 혼자 3상이다. 엥? 뭐지? 너무 기름지면 안된다면서요? 근데 쟤는 타겟이 기름바다에 위치해 있어서 기름져? 오히려 좋아! 인겁니다. AlogP가 낮은 애들 중에는 염기 짝퉁(이미테이션), 항생제(주사로 놓기때문에 기름질 필요가 없음), 올리고당이 보인다. 그러니까 누누이 말하는거지만 RO5를 지키면 좋다는거지 꼭 어길 필요가 없어요… 그 특성과 타겟에 따라서는 오히려 좋아! 인 경우도 있거든. 승인된 약물 중 EPTIFIBATIDE는 뱀독 유래 펩타이드다.
Name Status Type AlogP Type Withdrawn Flag
445 LUMIRACOXIB Approved Small molecule 4.16 Small molecule True
473 TOLRESTAT Approved Small molecule 3.56 Small molecule True
504 CERIVASTATIN Approved Small molecule 4.88 Small molecule True
564 XIMELAGATRAN Approved Small molecule 1.10 Small molecule True
849 VALDECOXIB Approved Small molecule 2.96 Small molecule True
954 UMBRALISIB Approved Small molecule 6.66 Small molecule True
1160 CERIVASTATIN SODIUM Approved Small molecule 4.88 Small molecule True
1389 NOMIFENSINE MALEATE Approved Small molecule 2.85 Small molecule True
1427 NOMIFENSINE Approved Small molecule 2.85 Small molecule True
1548 TOLCAPONE Approved Small molecule 2.55 Small molecule True
1578 APROTININ Approved Unknown NaN Unknown True
그… 전에도 얘기했지만 모든 약에는 부작용이 있어요. 근데 여기 플래그가 선 건 뭐다? 부작용이 진지하게 이거 투약하면 X될 것 같다… 수준이라는 얘깁니다.
1. CERIVASTATIN: 횡문근융해증(그 운동 무리하면 콜라색 소변 보잖아요? 그겁니다) 2. LUMIRACOXIB: 간독성 3. VALDECOXIB: 심혈관 질환(심장마비, 뇌졸중)의 위험성 증가 및 심각한 피부 반응 위험 4. XIMELAGATRAN: 와파린(항응고제)의 대체제였는데 아... 간독성 아... 5. TOLRESTAT: 간독성 6. UMBRALISIB: 어... 그... 항암제인데요... 다른 의미로 효과가 있었습니다... 7. NOMIFENSINE MALEATE: 항우울제인데... 아... - 급성 용혈성 빈혈: 내 몸의 면역계가 갑자기 내 적혈구를 적으로 오해해서 다 터뜨려버립니다. (피가 모자라!) - 노미펜신 열(Fever): 약만 먹으면 고열이 펄펄 끓습니다. - 심각한 간 독성: 간 수치가 수직 상승합니다. - 조증/환각: 정신과 약(항우울제)인데, 우울증 고치려다 사람이 너무 들떠서 조증이 오거나 환각을 봅니다. 8. TOLCAPONE: 간독성 9. APROTININ: 피를 너무 잘 멈춰서... 아...
그래서 감기약은 딜러가 아닌 서폿입니다. 증상을 좀 완화시켜서 면역계가 바이러스를 조질 수 있게 돕는 역할임.
창고털이
# 바이러스 서열 다운로드
# 이게 근데 막 받으면 안되거든요? 한타바이러스 식구들은 둘쨰치고 저기가 데이터가 진짜 방대해요.
virus_query = "Rhinovirus[Organism] AND VP1 AND complete cds AND 7000:7500[Sequence Length]"
print('Searching sequences... ') # 솔직히 이거 없으면 되는건지 불안하잖아요...
handle = Entrez.esearch(db="nucleotide", term=virus_query, retmax=40)
record = Entrez.read(handle)
id_list = record['IdList']
print(f"총 {len(id_list)}개의 표준 서열을 찾았습니다.")
print("====== MSA Result ======")
alignment = AlignIO.read("influenza_h3n2_muscle_aligned.fasta", "fasta") # FASTA 니네 확장자가 몇개냐...
for record in alignment:
print(f"{record.id[:10]:<15} : {record.seq[:100]}")
이거... 올려드리고 싶었는데요... 일단 출력이 짤렸고요... 이거 올려도 네이버에서는 짤림.
# 코어 시퀀스는 어디? (바이러스라고 앞뒤 안가리고 다 변형하는거 아님)
def calculate_conservation(alignment):
length = alignment.get_alignment_length()
scores = []
for i in range(length):
column = alignment[:, i]
most_common = max(column, key=column.count)
score = column.count(most_common) / len(column)
scores.append(score)
return scores
scores = calculate_conservation(alignment)
print(f"해당 구간의 평균 보존율: {np.mean(scores)*100:.2f}%")
아니 근데 보존율이 왜 98퍼가 뜸? 내가 로직을 조졌나?
섀넌 엔트로피
def calculate_shannon_entropy(alignment):
entropy_list = []
num_sequences = len(alignment)
alignment_length = alignment.get_alignment_length()
for i in range(alignment_length):
column = alignment[:, i]
# 각 염기(A, C, G, T, -)의 빈도 계산
counts = {base: column.count(base) for base in "ACGT-"}
entropy = 0
for base in counts:
p = counts[base] / num_sequences
if p > 0:
entropy -= p * math.log2(p)
entropy_list.append(entropy)
return entropy_list
# 1. MSA 결과 불러오기 (파일명을 맞춰주세요)
alignment = AlignIO.read("rhinovirus_muscle_aligned.fasta", "fasta")
entropy_values = calculate_shannon_entropy(alignment)
# 2. Sliding Window로 평활화 (가독성 향상)
window_size = 50
smoothed_entropy = np.convolve(entropy_values, np.ones(window_size)/window_size, mode='same')
# 3. 시각화 (NanumSquare 반영)
plt.figure(figsize=(15, 5))
plt.plot(smoothed_entropy, color='#2c3e50', linewidth=1)
plt.title(f'라이노바이러스 게놈 섀넌 엔트로피 분석 (Window Size: {window_size})', fontsize=15, pad=20)
plt.xlabel('게놈 위치 (Nucleotide Position)', fontsize=12)
plt.ylabel('엔트로피 (Bits)', fontsize=12)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
아, 이건 한타랑 코드 다릅니다. 한타꺼 그대로 해봤는데 한 1700bp에서 짤리데... 그래서 그래프도 달라요.
한타때는 뭔 시뻘건 잔디같은거 하나 있었죠? 걔는 잔디 높이가 높을수록 변이가 잘 되는 지역이었고 쟤는 피크가 위로 솟을수록 변이가 잘 되는 구역인겁니다. 보시면 피크가 들쭉날쭉하죠? 그리고 저기 수상학 낮은 부분도 보이죠? 그죠. 쟤들도 아무데나 변이하면 X되는거예요.
# 1. 거리 행렬 계산 (Identity 모델 사용)
calculator = DistanceCalculator('identity')
dm = calculator.get_distance(alignment)
# 2. Neighbor-Joining(NJ) 트리 생성
constructor = DistanceTreeConstructor(calculator, 'nj')
tree = constructor.build_tree(alignment)
tree.root_at_midpoint() # 루트를 중간으로 잡아 균형 잡힌 트리 생성
# 3. 시각화 (NanumSquare 폰트가 이미 글로벌 설정되어 있으므로 바로 출력!)
fig = plt.figure(figsize=(12, 8), dpi=100)
ax = fig.add_subplot(1, 1, 1)
plt.title("Rhinovirus Phylogenetic Tree (Based on Whole Genome)", fontsize=18, pad=20)
# Bio.Phylo를 이용한 트리 드로잉
Phylo.draw(tree, axes=ax, do_show=False, label_func=lambda n: str(n) if n.is_terminal() else "")
# 후처리: 축 숨기기 등 깔끔하게 정리
plt.axis('off')
plt.tight_layout()
plt.show()
나도 저거 이름으로 바꾸고 싶은데... 제미나이랑 같이 뭔 짓을 해봐도 안바껴...
def pairwise_identity(seq1, seq2):
matches = sum(a == b for a, b in zip(seq1, seq2) if a != '-' and b != '-')
length = sum(a != '-' and b != '-' for a, b in zip(seq1, seq2))
return matches / length if length > 0 else 0
def extract_clades(tree, cutoff=0.05):
clade_map = {}
clade_id = 0
for clade in tree.find_clades():
if clade.branch_length and clade.branch_length > cutoff:
terminals = clade.get_terminals()
for t in terminals:
clade_map[t.name] = f"Clade_{clade_id}"
clade_id += 1
return clade_map
clade_map = extract_clades(tree, cutoff=0.05)
# ID 정규화 (이거 중요)
normalized_clade_map = {}
for k, v in clade_map.items():
normalized_clade_map[k.split('.')[0]] = v
within_clade = []
between_clade = []
for rec1, rec2 in combinations(alignment, 2):
id1 = rec1.id.split('.')[0]
id2 = rec2.id.split('.')[0]
if id1 not in normalized_clade_map or id2 not in normalized_clade_map:
continue
identity = pairwise_identity(str(rec1.seq), str(rec2.seq))
if normalized_clade_map[id1] == normalized_clade_map[id2]:
within_clade.append(identity)
else:
between_clade.append(identity)
print(len(within_clade), len(between_clade))
u, p = mannwhitneyu(
within_clade,
between_clade,
alternative="greater"
)
print(f'U-statistic: {u:.1f}')
print(f'p-value: {p:.4e}') # 그... 이게... 맞아요?
얘가 NCBI에서 유전자 이름으로 찾은거다. 검색한 유전자 이름은 LHT(Lysine/Histidine Transporter) 1.
def plot_flexible_heatmap(alignment_file, target_id):
"""
alignment_file: MSA 결과 파일 (.aln)
target_id: 기준이 될 유전자 ID (예: 'LHT1' 또는 'At5g40780')
"""
align = AlignIO.read(alignment_file, "fasta")
# 1. 매개변수로 받은 target_id가 포함된 레코드 찾기
target_idx = -1
for i, rec in enumerate(align):
if target_id in rec.id:
target_idx = i
break
if target_idx == -1:
print(f"❌ 오류: 파일 내에서 '{target_id}'를 찾을 수 없습니다.")
return
target_seq = str(align[target_idx].seq)
names = [rec.id for rec in align]
identities = []
# 2. 기준 서열 vs 전체 서열 비교 (Gap-corrected)
for rec in align:
curr_seq = str(rec.seq)
matches = sum(1 for a, b in zip(target_seq, curr_seq) if a == b and a != "-")
valid_len = sum(1 for a, b in zip(target_seq, curr_seq) if a != "-" and b != "-")
identity = (matches / valid_len * 100) if valid_len > 0 else 0
identities.append(identity)
# 3. 데이터프레임 및 시각화
df = pd.DataFrame(identities, index=names, columns=[f'Standard: {target_id}'])
df = df.sort_values(by=df.columns[0], ascending=False)
plt.figure(figsize=(8, 10))
sns.heatmap(df, annot=True, fmt=".1f", cmap="Blues")
plt.title(f"Comparison based on {target_id}")
plt.tight_layout()
return df
plot_flexible_heatmap("ARATH_LHT_aligned.aln", "LHT1")
많이 다르죠? 오른쪽 히트맵도 본인 논문에 서플로 들어갔던거랑은 좀 다른데 그 정도는 뭐 아 10년동안 연구자들이 규명해서 달라졌구나 할 수 있는 정도거든요? 근데 쟤는 너무 다른거야. 그래서 지피티한테 이게 왜 다른지 물어봤는데, 식물 유전자로 분석할거면 NCBI 말고 식물 전용 DB를 털라고 하더라고. 애기장대면 TAIR라고 있으니까 거기 터십쇼.
[염색체 번호 기준 신원 확인 결과]
Paper_Name Locus_ID NCBI_Original_Name Match_Status
LHT1 AT5G40780 ❌ NCBI 리스트에 없음 데이터 누락
LHT2 AT1G24400 ❌ NCBI 리스트에 없음 데이터 누락
LHT3 AT1G61270 ❌ NCBI 리스트에 없음 데이터 누락
LHT4 AT1G47670 ❌ NCBI 리스트에 없음 데이터 누락
LHT5 AT1G67640 ❌ NCBI 리스트에 없음 데이터 누락
LHT6 AT3G01760 ❌ NCBI 리스트에 없음 데이터 누락
LHT7 AT4G36180 ❌ NCBI 리스트에 없음 데이터 누락
LHT8 AT1G71680 ❌ NCBI 리스트에 없음 데이터 누락
LHT9 AT1G48640 ❌ NCBI 리스트에 없음 데이터 누락
LHT10 AT1G25530 ❌ NCBI 리스트에 없음 데이터 누락
여기 들어가보면 아주 온갖가지 컬러팔레트가 다 있다. 그거랑 별개로 본인이 직접 만들 수도 있는데 이것도 나중에 설명해드림… 오늘은 여기서 제공해주는 것만 봅시다.
평범한 그래프
다채로운거 좋아하시면 tab10, tab20이 있다. 배리에이션으로 브라잇 다크 이런것도 있으니까 그런거 쓰십쇼. set2도 좋음. 내가 써보니까 Blues, Grays같은 단색은 막대그래프나 꺾은선그래프에서 색 변화도 좀 단조롭고 구별하기가 많이 빡셈. 그리고 단색 컬러맵은 시발점이 연한색이라 막대그래프나 꺾은선그래프에 뭐 얼마 없는데 화이트그리드 주잖아요? 주사터널현미경 예약해야하나 싶게 선이고 막대고 안보임. _r 주면 반대로 그리는거라 색이 진해지긴 한데, 그럼에도 불구하고 막대나 선끼리 구별 안 되는 게 단점이다.
히트맵
근데 막대그래프 그릴때는 눈뽕 오지게 오는 Blues나 Grays같은 단색 라인이 오히려 히트맵 그릴 때는 좋다. 히트맵은 그 뭐라해야되지? 색의 밝기? 명도? 그런걸로 구별하는거거든요. 그게 약간 큐알코드마냥 생긴 그래프인데 찐할수록 높은거고 연할수록 낮은거고 그래요. 근데 그런거에 set2나 tab10 쓴다? 눈뽕은 둘째치고 이게 뭐가 뭔지 구별이 안 된다. 히트맵 그릴때는 단색 파레트나 YlOrBr같은 거 쓰세요. 색깔에 따라 농도 구별이 명확해야 하는 그래프면 적어도 단색이나 밝은색->어두운색으로 가는 파레트로 쓰는 게 좋다. 마그마나 viridis도 괜찮았음.
양 끝단에 다른 색이 있는 파레트
본인은 쿨웜이나 아이스파이어 주로 쓰는 편인데, 어떤거 말하는거냐면
이거(밑에 있는게 아이스파이어) 그리고
이거(밑에 있는게 쿨웜). 이런 파레트들이 약간 반전요소? 그런거 넣기는 좋다. 예를 들어서 빨간색은 증가, 파란색은 감소 이런거. 근데 히트맵에서 쓰기는 좀 번잡시럽고 쿨웜같은 경우 다크그리드랑 병행하면 막대가 잘 안보임.
그 외에도 히트맵에 cmap 안 주면 기본으로 걸리는 마그마나 viridis도 좋은 선택이다. 저 두개는 히트맵에 써도 이쁨.
포켓몬에는 성비가 존재한다. 성비가 불명(무성)이거나 한쪽으로 쏠려있는 경우도 있지만 보통은 암수가 다 있는데 비율이 다른 경우가 많다. 님들 솔직히 세꿀버리 비퀸 만들어야되는데 암컷 안떠서 피눈물 흘리신 적 있으시죠? 난 있음... 아오 포고 야도뇽이 자꾸 수컷만 나와서 대환장파티여...
# p(성비): 7:1(0.875:0.125) / 1:1(0.5:0.5)
# n(트라이 수) = 3000 (100박스)
n = 3000
p1 = 0.125 # 암컷
p2 = 0.5
# 성비별 난수
binom_uneven = np.random.binomial(1, p1, n) # 알은 한번에 하나 나옵니다
binom_even = np.random.binomial(1, p2, n)
# 기댓값 n * p
np_uneven = n * p1
np_even = n * p2
display(f'7:1 평균 {np.mean(binom_uneven):.4f}, 기댓값 {np_uneven}')
display(f'5:5 평균 {np.mean(binom_even):.4f}, 기댓값 {np_even}')
'7:1 평균 0.1167, 기댓값 375.0'
'5:5 평균 0.5017, 기댓값 1500.0'
그 우리가... 알이... 한번에 하나나와... 그래서 저 코드가 뭔데요? 각 성비에 따라 암컷 포켓몬을 뽑기 위해서 3000번 시물 돌린 평균과 기대값이다. 성비가 언이븐한쪽이 좀 더 낮죠? 저 언이븐한쪽이 7:1이다. 일단 둘 다 암컷을 뽑는 걸로 전제하고 돌린게 이거다.
# 왔구나 이항분포오오오
# 성공횟수: 0~10
# 트라이: 10
# 확률: 0.125
for k in range(11):
p = stats.binom.pmf(k, 10, 0.125)
print(f'10번 트라이해서 {k}마리의 암컷 포켓몬을 얻을 확률: {p:.5f}')
10번 트라이해서 0마리의 암컷 포켓몬을 얻을 확률: 0.26308
10번 트라이해서 1마리의 암컷 포켓몬을 얻을 확률: 0.37582
10번 트라이해서 2마리의 암컷 포켓몬을 얻을 확률: 0.24160
10번 트라이해서 3마리의 암컷 포켓몬을 얻을 확률: 0.09204
10번 트라이해서 4마리의 암컷 포켓몬을 얻을 확률: 0.02301
10번 트라이해서 5마리의 암컷 포켓몬을 얻을 확률: 0.00394
10번 트라이해서 6마리의 암컷 포켓몬을 얻을 확률: 0.00047
10번 트라이해서 7마리의 암컷 포켓몬을 얻을 확률: 0.00004
10번 트라이해서 8마리의 암컷 포켓몬을 얻을 확률: 0.00000
10번 트라이해서 9마리의 암컷 포켓몬을 얻을 확률: 0.00000
10번 트라이해서 10마리의 암컷 포켓몬을 얻을 확률: 0.00000
10번 해서 이브이 암컷 한마리 뽑을 확률이 0.37...이니까 30%라는 얘기다. 힘내십셔.
# 왔구나 이항분포오오오
# 성공횟수: 0~10
# 트라이: 10
# 확률: 0.5
for k in range(11):
p = stats.binom.pmf(k, 10, 0.5)
print(f'10번 트라이해서 {k}마리의 암컷 포켓몬을 얻을 확률: {p:.5f}')
10번 트라이해서 0마리의 암컷 포켓몬을 얻을 확률: 0.00098
10번 트라이해서 1마리의 암컷 포켓몬을 얻을 확률: 0.00977
10번 트라이해서 2마리의 암컷 포켓몬을 얻을 확률: 0.04395
10번 트라이해서 3마리의 암컷 포켓몬을 얻을 확률: 0.11719
10번 트라이해서 4마리의 암컷 포켓몬을 얻을 확률: 0.20508
10번 트라이해서 5마리의 암컷 포켓몬을 얻을 확률: 0.24609
10번 트라이해서 6마리의 암컷 포켓몬을 얻을 확률: 0.20508
10번 트라이해서 7마리의 암컷 포켓몬을 얻을 확률: 0.11719
10번 트라이해서 8마리의 암컷 포켓몬을 얻을 확률: 0.04395
10번 트라이해서 9마리의 암컷 포켓몬을 얻을 확률: 0.00977
10번 트라이해서 10마리의 암컷 포켓몬을 얻을 확률: 0.00098
저거 시각화해보니까 약간 정규분포같이 생겼던데? 아무튼 1:1일때는 반반 확률이 제일 높다.
이로치와 이항분포
그 위에 게시글에서 1/4096, 1/1365, 1/512로 했었죠? 그거 할 거다.
1/4096
# 확률: 1/4096
# 트라이: 3000
p = 1 / 4096
for k in range(11):
shiny = stats.binom.pmf(k, 3000, p)
print(f'3000번 트라이해서 {k}마리의 이로치를 만날 확률: {shiny:.5f}')
3000번 트라이해서 0마리의 이로치를 만날 확률: 0.48070
3000번 트라이해서 1마리의 이로치를 만날 확률: 0.35216
3000번 트라이해서 2마리의 이로치를 만날 확률: 0.12895
3000번 트라이해서 3마리의 이로치를 만날 확률: 0.03147
3000번 트라이해서 4마리의 이로치를 만날 확률: 0.00576
3000번 트라이해서 5마리의 이로치를 만날 확률: 0.00084
3000번 트라이해서 6마리의 이로치를 만날 확률: 0.00010
3000번 트라이해서 7마리의 이로치를 만날 확률: 0.00001
3000번 트라이해서 8마리의 이로치를 만날 확률: 0.00000
3000번 트라이해서 9마리의 이로치를 만날 확률: 0.00000
3000번 트라이해서 10마리의 이로치를 만날 확률: 0.00000
그 된장맛 식스테일은 대체 어떻게 뜬 거지? 아니 예전에 소드실드 도감작하다가 식스테일 이로치 하나 잡았거든요.
1/1365
# 확률: 1/1365 (빛부)
# 트라이: 3000
p = 1 / 1365
for k in range(11):
shiny = stats.binom.pmf(k, 3000, p)
print(f'3000번 트라이해서 {k}마리의 이로치를 만날 확률: {shiny:.5f}')
3000번 트라이해서 0마리의 이로치를 만날 확률: 0.11096
3000번 트라이해서 1마리의 이로치를 만날 확률: 0.24404
3000번 트라이해서 2마리의 이로치를 만날 확률: 0.26828
3000번 트라이해서 3마리의 이로치를 만날 확률: 0.19656
3000번 트라이해서 4마리의 이로치를 만날 확률: 0.10797
3000번 트라이해서 5마리의 이로치를 만날 확률: 0.04743
3000번 트라이해서 6마리의 이로치를 만날 확률: 0.01736
3000번 트라이해서 7마리의 이로치를 만날 확률: 0.00544
3000번 트라이해서 8마리의 이로치를 만날 확률: 0.00149
3000번 트라이해서 9마리의 이로치를 만날 확률: 0.00036
3000번 트라이해서 10마리의 이로치를 만날 확률: 0.00008
이건 빛나는부적+야생이라고 보시면 된다.
1/512
# 확률: 1/512 (빛부+국제교배)
# 트라이: 3000
p = 1 / 512
for k in range(11):
shiny = stats.binom.pmf(k, 3000, p)
print(f'3000번 트라이해서 {k}마리의 이로치를 만날 확률: {shiny:.5f}')
3000번 트라이해서 0마리의 이로치를 만날 확률: 0.00284
3000번 트라이해서 1마리의 이로치를 만날 확률: 0.01665
3000번 트라이해서 2마리의 이로치를 만날 확률: 0.04887
3000번 트라이해서 3마리의 이로치를 만날 확률: 0.09557
3000번 트라이해서 4마리의 이로치를 만날 확률: 0.14013
3000번 트라이해서 5마리의 이로치를 만날 확률: 0.16432
3000번 트라이해서 6마리의 이로치를 만날 확률: 0.16051
3000번 트라이해서 7마리의 이로치를 만날 확률: 0.13435
3000번 트라이해서 8마리의 이로치를 만날 확률: 0.09837
3000번 트라이해서 9마리의 이로치를 만날 확률: 0.06399
3000번 트라이해서 10마리의 이로치를 만날 확률: 0.03746
혹시 이거 보면서 왜 하필 3000트일지 궁금하지 않았습니까? 포켓몬 인게임 내에는 박스가 있는데… 박스 하나에 포켓몬이 30마리 들어갑니다. 포켓몬 홈이 6000마리까지 되잖아요? 박스가 200개 있습니다. 그래서 박스 단위 끊는다고 3000트가 된 거다. 3000마리면 100박스임.
그리고 한가지 더 알아두셔야 하는 게 있는데, 1/4096이나 1/1365와 달리 1/512는 알까기 트라이 수이다. 전제에 국제교배가 붙어있는데 그게 '어버이 국적이 다른 두 포켓몬'을 교배시켜서 얻는거라 가끔 외국 유저랑 6V 메타몽을 바꾸기도 한다. 나는 한국어니까 한국어가 아닌 메타몽(혹은 한국어가 아닌 포켓몬)과 교배를 해야 저 방법이 성립하거든.
팔로워 수에 차이가 있으니까 정규화해서 비중을 봐야 한다. 그 포켓몬중에 지가르데라고 있는데, 얘가 퍼펙트폼으로 폼체인지를 할 때 피 회복이 되거든요? 신기하지 않습니까? 폼체인지를 하는데 피가 회복이 돼.
사실은 피 회복이 되는 게 아니라, HP 종족값이 늘어나서 최대 피통이 바뀐 것 때문에 체력이 회복되는 것처럼 보이는 것이다. 쉽게 말하자면 체력이 100인데 60을 잃으면 60%를 잃는거죠? 근데 체력이 200인데 60 잃은건 30%잖아요. 그런거임. 만 명 있는데 100명인거랑 1000명 있는데 100명 있는거랑은 엄연히 갭이 있기 때문에 정규화를 하는거라고 보시면 된다.
plt.figure(figsize=(10, 6))
# 1900s vs 2000s 인기도 분포 비교
sns.boxplot(data=song_df, x='Era_year', y='popularity', hue='Era_year', palette='Set2', legend=False)
plt.title('20세기 vs 21세기 곡 인기도 분포 비교', fontsize=16)
plt.show()
최신곡이라고 무조건 인기가 있는 건 아니기에… 뭐 요즘 레트로 열풍 이런것도 불고 있잖습니까. 근데… 솔직히 저 이상치 뭔지 궁금하지 않아요?
song_df.query('popularity > 80 and Era_year == "1900s"').sort_values('popularity', ascending=False).head()
솔직히 머라이어 캐리는 인정해야죠들. 우리나라에는 장범준의 벚꽃연금이 있다면 캐리언니는 크리스마스 연금 받는 분이심. 크리스마스마다 해동하잖음 ㅇㅇ
song_df.query('popularity > 80 and Era_year == "2000s"').sort_values('popularity', ascending=False).head()
plt.figure(figsize=(12, 8))
# 20세기 곡들의 댄스빌리티와 에너지 관계도
sns.scatterplot(data=song_1990, x='danceability', y='energy', hue='popularity', size='popularity', alpha=0.5, sizes=(20, 200))
# 인기도 상위 5개 곡에 이름표 달아주기
for i in range(5):
target = era_boss_songs.iloc[i]
plt.text(target['danceability']+0.01, target['energy']+0.01, target['name'],
fontsize=10, weight='bold')
plt.title('20세기 띵곡들의 오디오 특징 분포', fontsize=16)
plt.show()
이거 저기 친구네 실험실 가서 현미경 빌려와야것는디?
# 인기도 80 이상만 명확하게 표시
top_classics = song_1990[song_1990['popularity'] >= 80]
plt.figure(figsize=(12, 8))
# 전체 배경은 연하게
sns.scatterplot(data=song_1990, x='danceability', y='energy', color='lightgrey', alpha=0.1)
# 띵곡들만 진하게
sns.scatterplot(data=top_classics, x='danceability', y='energy',
hue='popularity', size='popularity', palette='rocket', sizes=(50, 300))
plt.title('1990년대의 띵곡')
plt.show()
연한 놈이 범인이여. 개인적으로 이런 그래프는 마그마나 비리디스보단 블루스, 그레이스같은 단색이 더 무난한 것 같다.
보라색이 있었으면 말을 하지 그래... 아무튼 여러분들은 히트맵이나 이런 산점도 그릴때 가급적 단색 쓰세요. 극단적인 변화량을 보여주고 싶으면 쿨웜이나 아이스파이어도 좋긴 한데 색깔 여러개면 눈뽕옴.
# 인기도 80 이상만 명확하게 표시
top_classics = song_2000[song_2000['popularity'] >= 80]
plt.figure(figsize=(12, 8))
# KDE Plot (밀도 등고선) 추가
sns.kdeplot(data=song_2000, x='danceability', y='energy', levels=5, color="black", linewidths=1)
# 전체 배경은 연하게
sns.scatterplot(data=song_2000, x='danceability', y='energy', color='lightgrey', alpha=0.1)
# 띵곡들만 진하게
sns.scatterplot(data=top_classics, x='danceability', y='energy',
hue='popularity', size='popularity', palette='Purples', sizes=(50, 300))
plt.title('2000년대의 띵곡')
plt.show()
2000년대는 좀 더 등고선이 위로 올라간 느낌이다. 근데 이거 왜케 오래걸림?
아티스트별 띵곡 찾기
마이클 잭슨
# 구조된 마이클 잭슨 곡의 '민낯' 공개
mj_the_one = song_df[song_df['artists'].str.contains('Michael Jackson', case=False, na=False)].sort_values('popularity', ascending=False)
# 데이터 수치 확인 (NanumSquare 폰트로 출력!)
display(mj_the_one[['name', 'year', 'popularity', 'danceability', 'energy', 'valence']][0:5])
아 나도 마잭은 알아요 이 사람들아.
에드시런
# 에드시런 찾아 삼만리
mj_the_one = song_df[song_df['artists'].str.contains('Ed Sheeran', case=False, na=False)].sort_values('popularity', ascending=False)
# 데이터 수치 확인 (NanumSquare 폰트로 출력!)
display(mj_the_one[['name', 'year', 'popularity', 'danceability', 'energy', 'valence']][0:5])
셀레스티얼은 밀렸군… 근데 shape of you도 좋음. 셀레스티얼은 스칼렛/바이올렛 엔딩 크레딧이기도 하고, 이 곡이 본가에 최초로 삽입된 '보컬 있는' 곡입니다. 기적은 가사는 있는데 보컬이 없음.
followers popularity
count 7.144000e+04 71440.000000
mean 2.330490e+05 28.773138
std 2.204255e+06 18.006693
min 0.000000e+00 0.000000
25% 9.127500e+02 15.000000
50% 7.865000e+03 28.000000
75% 4.510750e+04 41.000000
max 1.685087e+08 100.000000
id name genres main_genre
count 71440 71438 71440 71440
unique 71440 69643 16134 10
top 7yhRUp1m94EmlkPRw7VoVQ Chris Martin [] Electronic
freq 1 6 19580 20032
id name album_name artists lyrics genre artist_ids niche_genres
count 550622 550619 550602 550622 550622 550622 550622 550622
unique 550622 351146 140154 95121 478160 10 95661 23638
top 036JzAN5DCANSZyeW6MjqG Home Greatest Hits ["Grateful Dead"] Who's gonna tell you when\n It's too late?\n ♪... Rock ["4TMHGUX5WI7OOm53PqSDAT"] []
freq 1 221 1958 1500 56 197168 1500 6099
.isna().sum()
id 0
name 2
followers 0
popularity 0
genres 0
main_genre 0
dtype: int64
id 0
name 3
album_name 20
artists 0
danceability 0
energy 0
key 0
loudness 0
mode 0
speechiness 0
acousticness 0
instrumentalness 0
liveness 0
valence 0
tempo 0
duration_ms 0
lyrics 0
year 0
genre 0
popularity 0
total_artist_followers 0
avg_artist_popularity 0
artist_ids 0
niche_genres 0
dtype: int64
이거 걍 언노운으로 때우면 안되나? 이따 함 보긴 하겠지만.
.head()
id name followers popularity genres main_genre
0 6YROFUbu5zRCHi2xkir5pk Brian Hyland 67223 47 [] Pop
1 5tFRohaO5yEsuJxmMnlCO9 Barns Courtney 602647 62 [] Electronic
2 3w1Q754jb31h5CXQCcnLNL Capcom Sound Team 210392 58 ['japanese vgm', 'soundtrack'] Electronic
3 3oDbviiivRWhXwIE8hxkVV The Beach Boys 5139194 76 ['baroque pop'] Classical
4 60zvRmhQHRxokEB1taAVpN Beth Malone 1569 29 ['musicals'] Classical
id name album_name artists danceability energy key loudness mode speechiness ... tempo duration_ms lyrics year genre popularity total_artist_followers avg_artist_popularity artist_ids niche_genres
0 0Prct5TDjAnEgIqbxcldY9 ! UNDEN!ABLE ["HELLYEAH"] 0.415 0.605 7 -11.157 1 0.0575 ... 100.059 79500 He said he came from Jamaica,\nhe owned a coup... 2016 Rock 0 769490 52.0 ["4hxDvVq5t8ebPYPdBl1F9f"] ["groove metal", "metal"]
1 2ASl4wirkeYm3OWZxXKYuq !! Childhood Dreams ["Yxngxr1"] 0.788 0.648 7 -9.135 0 0.3150 ... 79.998 114000 Fuck the bitch, now she running with my kids\n... 2019 Hip-Hop 29 143628 45.0 ["2jwRHcdgkRhelYEMqndDKe"] []
2 5tA3ImW310llKo8EMBj2Ga !!Noble Stabbings!! Situationist Comedy ["Dillinger Four"] 0.171 0.957 2 -5.749 1 0.1490 ... 175.317 197400 You like to stand on the other side\nPoint and... 2002 Rock 0 36619 35.0 ["4YAN46l70QV0PGXlMg0iHi"] ["melodic hardcore", "pop punk", "punk", "skat...
3 0fROT4kK5oTm8xO8PX6EJF !I'll Be Back! !I'll Be Back! ["Ril\u00e8s"] 0.823 0.612 1 -7.767 1 0.2480 ... 142.959 178533 It's been a while, shit, I missed the rehab, p... 2018 Hip-Hop 43 929303 63.0 ["6pdcQa7by8IKuoVXvgknlI"] ["french rap"]
4 1xBFhv5faebv3mmwxx7DnS !Lost! !Lost! ["Ril\u00e8s"] 0.729 0.552 7 -8.562 0 0.0650 ... 86.103 186197 I would like to give you all my time\nI would ... 2018 Hip-Hop 0 929303 63.0 ["6pdcQa7by8IKuoVXvgknlI"] ["french rap"]
근데 우리가 얘만 봐서는 이게 분포가 어떤지 몰라요. 그니까 빡스플롯을 그려보자 이거다. 근데 이제 스웜플롯을 곁들인.
# boxplot
sns.boxplot(data_df, x = 'Status', y = 'Molecular Weight', hue = 'Status')
sns.swarmplot(data=data_df, x='Status', y='Molecular Weight', color='red', alpha=0.5)
plt.title('Molecular Weight by Status')
plt.show()
보니까 clinical에 있는 이상치들은 그래도 500은 안 넘었는데, 저기 500 넘은 놈이 하나 있네? 너 누구야!
범인은 FENOPROFEN CALCIUM이라는 놈인데, 얘는 그 자체로는 분자량이 그렇게 크지 않다. 근데 왜 500이 넘는데요? 쟤가 구조상 카복실산(COOH)이 달려있는데 카복실산이 음전하거든요? 그니까 단독으로 두면 안정하지 아니할 거 아니예요. 그래서 칼슘이랑 걔랑 뭔 상관이냐고? 칼슘이 2가 양이온이니까 저 페노프로펜이라는 친구를 양 옆구리에 하나씩 끼고 있는거다.
clinical에 있는 이상치들은 다 뭘 붙여서 몸집이 커진 케이스.
Bioactivities
약을 먹으면 우리 몸에 들어와서 일을 하겠죠? 그 활성 정도를 말한다.
data_df.groupby('Status')['Bioactivities'].mean() # 평균
# boxplot
sns.boxplot(data_df, x = 'Status', y = 'AlogP', hue = 'Status')
sns.swarmplot(data=data_df, x='Status', y='AlogP', color='red', alpha=0.5)
plt.title('AlogP by Status')
plt.show()
엎은 애들이랑 승인됨에는 없는데 임상 진행중인 애들중에 이상치가 꽤 보인다. 저 중에서 하나는 동물용 소염진통제, 하나는 항진균제(바르는거)이고 나머지 두 개는 정보가 없음.
Withdrawn Flag
모든 약에는 부작용이 있다. 이 플래그는 부작용 관련된건데... 아니 근데 다 부작용이 있는데 저 플래그는 뭐임? 그 부작용에도 '정도'가 있어요. 그 부작용이 아 이거 터지면 사람 죽는다 내지는 와 이거 심각한데 수준이면 저 플래그가 선다고 보면 된다. 대표적인 예시가 탈리도마이드.
- Benoxaprofen: 간 괴사, 광과민성, 담즙정체성 황달 - Suprofen: 신장 독성 (옆구리 통증 증후군) - Pirprofen: 심각한 간독성 - Indoprofen: 위장관 출혈 및 발암성 의혹
이런 이유로 승인됐다가 시장에서 빠졌다고 보면 된다. 얘들도 나중에 다른 쓸모가 발견된다면 다시 쓰일지는 모르겠지만… 탈리도마이드도 다른 쓸모를 찾았음.
HBA, HBD
# FacetGrid를 사용해 Status별로 나눠서 보기
g = sns.FacetGrid(data_df, col="Status", height=4, aspect=1.2, col_order=['Approved', 'Clinical', 'Failed'])
g.map_dataframe(sns.histplot, x="HBA", y="HBD", discrete=(True, True), cbar=True, cmap="YlGnBu")
g.set_axis_labels("HBA (Acceptors)", "HBD (Donors)")
g.set_titles("{col_name} Group")
plt.tight_layout()
plt.show()
클리니컬은 뭔가 중구난방이구만. 참고로 둘다 5 넘어가면 안된다.
RO5 violation
data_df.groupby(['Status','#RO5 Violations']).size() # 분포 왜이래요
롸? 데이터에 뭐가 어떻게 들어있길래 그래요? 저기에는 우리가 살면서 봐 왔던(혹은 사 왔던, 혹은 거쳐 간) 모든 게임기 기종이 들어있다. 엑박은 세종류인데 닌텐도만 해도 게임보이 GBA(게임보이 어드밴스) DS 3DS 위… 그래서 이걸 제조사별로 묶는 칼럼을 하나 만드는거다. 예를 들어서 플스는 소니 이런 식으로. 그래야 플엑닌 보지.
아니 뭐 이딴것까지 묶어요? 라고 생각하실 수도 있는데, 1990년대에 발매된 게임들을 볼 때 1990년부터 1999년까지 찾는 개노가다를 분석하는 코드에서 하고 싶지 않으면 할 수 있을 때 묶으십쇼.
플.엑.닌 삼국지
플.엑.닌은 플레이스테이션(공기청정기 아님), 엑스박스, 닌텐도다. 겜덕후들이라면 적어도 저 셋 중 하나 이상은 집에 있거나 구매 의사가 있을수도 있고, 겜덕후가 아니더라도 한번쯤은 들어봤으며, 특히 애 있는 집안에서 애들 시켜주면 껌뻑 죽는 그거 맞다. 얘네들이 하루아침에 응애 나 닌텐도! 이러고 나온 게 아니라 각각 태동기, 전성기, 흑역사(...)가 있고 특징이 있단말이죠. 그니까 함 보자.
얼마나 팔았나
# 전체 플랫폼(회사)별로 몇 개나 있나요?
video_df.groupby('Platform_Vendor')[['Rank']].count()
닌텐도 얘네는 뭐 게임 찍어내나요? 그게 아니라 닌텐도가 역사가 조낸 길어요... 나중에 얘기해드림.
# 플. 엑. 닌은 각각 얼마나 팔았을까?
console_three = ['Nintendo','Sony','Microsoft'] # 엑박이 마소꺼예요..
video_df.query('Platform_Vendor in @console_three').groupby('Platform_Vendor')[['Rank']].count()
아니 그럼 마소는 왜이렇게 없어요? 빌형 뭐함? 마소(엑박)는 닌텐도나 소니(플스)에 비해 늦게 나온 애들이라 그래요.
# 년도별로는 얼마나 팔았음?
console_three = ['Nintendo','Sony','Microsoft']
video_df.query('Platform_Vendor in @console_three').groupby(['Platform_Vendor', 'Era'], observed=False)[['Global_Sales']].sum().sort_values(by=['Platform_Vendor', 'Global_Sales'], ascending=False)
년도별 판매량을 보면 닌텐도와 소니 둘 다 1980년부터 찍히지만 마소는 1990년대부터 찍히죠? 엑박이 플스, 닌텐도에 비해 후발주자다. 80년대 데이터를 비교해보면 소니보다 닌텐도가 더 많은데, 플.엑.닌중에는 닌텐도가 제일 먼저 나왔다.
# 다른 나라에서도 그럴까요?
# NA: 북미, EU: 유럽, JA: 일본, Other: 기타
console_three = ['Nintendo','Sony','Microsoft']
video_df.query('Platform_Vendor in @console_three').groupby(['Platform_Vendor', 'Era'], observed=False)[['NA_Sales','EU_Sales','JP_Sales','Other_Sales','Global_Sales']].sum().sort_values(by=['Platform_Vendor','Era'], ascending=False)
칼럼은 왼쪽부터 북미, 유럽, 일본, 기타(한국도 포함인듯), 글로벌이다. 아니 닌텐도 북미에서 생각보다 많이 해먹었네?
import matplotlib.pyplot as plt
import seaborn as sns
# 1. 그래프용 데이터 준비 (Global 제외)
plot_df = df_2000s_region[['NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales']]
# 2. 그래프 그리기
# 'stacked=False'가 핵심입니다 (기본값이 False라 생략 가능)
ax = plot_df.plot(kind='bar',
figsize=(12, 7),
width=0.8, # 막대 두께 조절
rot=0) # x축 이름(Nintendo 등) 똑바로
# 3. 디테일 설정
plt.title('2000s Regional Sales Comparison: Sony vs Nintendo vs Microsoft', fontsize=16, pad=20)
plt.ylabel('Sales (Millions)', fontsize=12)
plt.xlabel('Platform Vendor', fontsize=12)
plt.legend(title='Region', fontsize=10)
plt.grid(axis='y', linestyle='--', alpha=0.5)
# 막대 위에 수치 표시 (가독성 업그레이드)
for p in ax.patches:
ax.annotate(f'{p.get_height():.1f}',
(p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center',
xytext = (0, 7),
textcoords = 'offset points',
fontsize=9)
plt.tight_layout()
plt.show()
소니는 유럽, 기타 지역에서 닌텐도를 앞섰고 엑박은 일본에서 맥을 못 추죠? 이건 마소가 단순히 늦게 시작해서가 아니라, 얘네들이 엑박을 내놓긴 했는데 일단 컨트롤러가 서양인들 손 위주라서 동양인들에게는 너무 컸음. 그 저기 서양 형님들 떡대 봐봐요 장난아님.
장르별 실적+주력 장르
# 글로벌 판매량 기준으로 장르별 실적 비교
genre_pivot = (video_df.query('Platform_Vendor in @console_three')
.groupby(['Platform_Vendor', 'Genre'], observed=False)['Global_Sales']
.sum()
.unstack())
# 가독성을 위해 배경색 입히기 (주피터 노트북 환경)
genre_pivot.style.format("{:.3f}").background_gradient(cmap='coolwarm', axis=1)
마소는 슈터하고 액션, 닌텐도는 플랫폼과 액션, Misc(뭐임?), 스포츠, 롤플레잉, 소니는 액션이랑 스포츠. 이 장르들에서 많이 해먹었다.
실제로도 보면 세 콘솔에서 유명한 게임이 다 다른데, 닌텐도는 마리오+젤다+동숲+스플래툰+커비를 비롯한 퍼스트 파티와 포켓몬(퍼스트 파티는 아님) 등, 다양한 IP를 활용해서 다양한 게임을 내는데 이 게임들을 닌텐도 기기를 사야만 할 수 있다. 그러니까 이거 할 거면 우리 기기를 반드시 사라! 이거임. 그리고 다양한 IP를 활용해서 다양한 게임들을 내기때문에 주력이라고 할 수 있을 장르도 여러개다.
플스는 내가 안해봐서(플스 잡아본 적 없음) 구글링했는데, 플스의 주력 게임 중 하나가 파이널 판타지이다. 티파! 에어리스! 그거 맞음. 달을 보고 있었다가 파판 16 OST인가 그래요. 그 켄시가 부른거 함 들어보십쇼. 그 외에도 갓 오브 워나 마블 스파이더맨, 호라이즌 제로 던도 있고 동아리에서 선배들이 많이 했던 위닝도 있었다.
엑박은 가장 많이 떠올리시는게 헤일로, 기어스 오브 워인데 둘다 일단 총겜이고... 데드스페이스도 엑박에서 나오지 않았나? 그리고 포르자 시리즈도 있다. 포르자는 총겜은 아님.
시대, 그리고 게임
# 이제 그룹바이 기준이 연도로 바뀌게 된다. 단, 연도가 0으로 된 것은 빼고. (발매 취소된 겜이라... )
video_df_cp = video_df.copy()
video_df_cp.drop(video_df_cp[video_df_cp['Year'] == 0].index, inplace=True)
video_df_cp['Era'] = video_df_cp['Era'].cat.remove_unused_categories()
video_df_cp.groupby(['Era', 'Platform'], observed=False)['Name'].count().reset_index(name='Game_Count')
저 데이터 안에 연도가 NaN인 게임 중 하나가 발매 취소된 게임이다. 그래서 퍼블리셔는 있는데 발매년도가 없다.
피벗테이블이 가로로 너무 길어서 이건 캡쳐 못했음. 그럼 닌텐도가 왜 북미에서 짱짱맨이 되었는지를 얘기해보자.
아타리 쇼크를 아십니까? 들어본 사람도 있을거고, 그 시대에 살았던 사람도 계실거고, 처음 듣는 사람도 있을텐데 닌텐도 이전에 아타리가 있었다. 근데 년도를 잘 보니까 80년대에 나왔다가 90년대부터 판매량이 0이네요? 아타리 쇼크가 83년부터 85년까지였거든…
아타리가 플랫폼을 만드는 회사라는 건 알겠어. 근데 아타리 쇼크는 뭐고, 닌텐도가 어떻게 그 자리를 차지한거지? 에 대한 얘기를 하려면 아타리 쇼크 얘기를 해야 한다. 엔씨 아시죠 엔씨? 그 리니지 만드는 회사. 걔네가 처음부터 그렇게 욕먹던 회사가 아니었고, 블소 초창기에는 진짜 갓겜 만드는 회사였어요. 근데 지금은 게임 낸다고 쇼케이스 나오면 다들 반응이 그래서 과금 모델은 어떤데? 어차피 리니지라이크 아냐? 이러잖아. 나도 엔씨게임은 진짜 안하지만 갓겜시절에는 블소 즐기고 그랬거든요.
그래서 송재경님을 만난다면 하나 꼭 물어보고 싶은 게 '개고기탕후루'라는 말을 듣고 어떤 심정이었는지임. 개고기탕후루가 멸칭입니다, 멸칭. 아마 택진이형은 최초로 개고기탕후루라는 말을 만들어낸 사람을 진짜 애타게 찾고싶을거임.
갑자기 엔씨 얘기가 왜 나옴? 얘네가 리니지 블소 다 갓겜이었는데 개고기탕후루 소리를 왜 들어요? 그죠. 게임이 그놈이 그놈인데 리니지라이크에 과금은 무슨 차 한대값을 박아야 게임 속에서 숨을 쉴 수 있잖아요. 아타리도 비슷한 이유로 망했다고 보시면 됩니다. 엔씨는 리니지라이크가 돈이 되니까 그것만 찍어냈다가 개고기탕후루 소리를 듣는거고, 아타리는 게임이 돈이 된다고 서드파티들이 겁나 찍어냈는데 그게 지금 엔씨게임이랑 비교하는것조차 실례일 정도로 진짜 쌉똥겜 투성이라 그렇게 된 거다. 엔씨 영업이익 떨어졌다고 가끔 기사 뜨죠? 아타리 쇼크때 아타리는 그냥 조낸 망했습니다.
이 때 미국 게임 시장에 처음으로 뛰어든 선구자가 닌텐도였다. 근데 그때 사람들이 하도 개똥겜들을 많이 봐서 게임을 무슨 악마의 똥같은 걸로 취급하는 걸 본 닌텐도가 저희는 게임기가 아니라 엔터테인먼트 시스템(Entertainment System)입니다~ 하고 들어가서 북미 시장을 먹은거다.
그리고 그 '엔터테인먼트 시스템'이 NES다.
그 뒤에 소니가 쓱 참전해서 플.엑 구도가 된거고
빌형은 왜 거기 낀거임 근데?
의 압축버전.
플랫폼별 일짱은 누구?
벤더별 일짱
# 1. 각 플랫폼별 Global_Sales가 가장 높은 행의 인덱스 추출
idx = video_df.groupby('Platform')['Global_Sales'].idxmax()
# 2. 해당 인덱스의 데이터만 추출하여 정리
top_titles = video_df.loc[idx, ['Platform_Vendor', 'Platform', 'Name', 'NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales', 'Global_Sales']]
# 3. 제조사(Vendor)와 플랫폼별로 보기 좋게 정렬
top_titles = top_titles.sort_values(['Platform_Vendor', 'Global_Sales'], ascending=[True, False])
# 소수점 3자리 설정 및 출력
top_titles.style.format({
'NA_Sales': '{:.3f}', 'EU_Sales': '{:.3f}',
'JP_Sales': '{:.3f}', 'Other_Sales': '{:.3f}', 'Global_Sales': '{:.3f}'
}).background_gradient(cmap='coolwarm', subset=['Global_Sales'])
우리집에 위는 없었지만 동방에는 있었다. 근데 이게 신박하긴 했음. 지금도 스위치 조이콘 빼서 휘두른다거나 그런거 있죠? 점프로프 챌린지도 그렇고. 그걸 나는 동아리방에서 위 리모콘으로 마리오카트랑 복싱 하면서 처음 써봤음. 근데 그런것치고 압도적인데…?
포켓몬 XY같은 경우 처음으로 3DS로 나온거고 모든 포켓몬이 3D화 됐고, 피카츄의 울음소리가 성우 울음소리로 바꼈는데 왜 롤백했냐 아오... 그래서 1세대~5세대 포켓몬은 버전별 이미지 찾아보면 도트에서 3D 됐잖아요. 라이츄 똥배때문에 도트뚱쥐됐는데 3D되면서 살 빠진거임. 동생놈이 염무왕의 존재를 알았다면 내 별명은 염무왕이었겠지... 아무튼. 그리고 포켓몬 XY, ORAS, SM, USUM에 있는 시스템 중에 포켓몬 아미에(7세대는 리플레)라는 게 있다. 내 포켓몬을 쮸담쮸담하고 하이파이브고 하고 뭐 그런거 있음. 그것때문에 잘 팔린게 아닐까 추정중이다.
플랫폼별 일짱
# 1. 각 플랫폼별 매출 1위 게임 추출
top_idx = video_df_cp.groupby('Platform')['Global_Sales'].idxmax()
platform_tops = video_df_cp.loc[top_idx, ['Platform', 'Name', 'NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales', 'Global_Sales']]
# 2. 보기 편하게 판매량 순으로 정렬 (상위 15개 기종)
platform_tops = platform_tops.sort_values('Global_Sales', ascending=False).head(15)
# 3. 소수점 3자리 조절 및 스타일링
platform_tops.style.format("{:.3f}", subset=['NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales', 'Global_Sales'])\
.background_gradient(cmap='coolwarm', subset=['Global_Sales'])\
.bar(subset=['NA_Sales', 'EU_Sales', 'JP_Sales'], color='#FFA07A')\
.bar(subset=['Other_Sales'], color='#FFD700')
포켓몬이 3위야 세상에...
# 시각화를 위해 게임 이름을 인덱스로 설정
plot_data = platform_tops.set_index('Name')[['NA_Sales', 'EU_Sales', 'JP_Sales', 'Other_Sales']]
# 누적 막대 그래프 그리기
plot_data.plot(kind='barh', stacked=True, figsize=(12, 8), colormap='coolwarm')
plt.title('Sales Breakdown by Region for Top Titles per Platform', fontsize=15)
plt.xlabel('Sales (Millions)')
plt.ylabel('Top Game Title')
plt.legend(title='Region', bbox_to_anchor=(1.05, 1), loc='upper left')
plt.show()
저는 wii를 안해봐서 쟤가 왜 잘 팔렸는지 모르겠습니다.
플랫폼, 그리고 시대별 일짱은?
# 1. Vendor와 Era별로 Global_Sales가 최대인 행의 인덱스를 추출
# observed=True를 써서 데이터가 없는 Unknown 시대는 제외합니다.
top_idx_era = video_df_cp.groupby(['Platform_Vendor', 'Era'], observed=True)['Global_Sales'].idxmax()
# 2. 해당 인덱스로 데이터프레임 생성
era_champions = video_df_cp.loc[top_idx_era,
['Platform_Vendor', 'Era', 'Platform', 'Name', 'NA_Sales', 'JP_Sales', 'Global_Sales']]
# 3. 가독성을 위해 정렬
era_champions = era_champions.sort_values(['Platform_Vendor', 'Era'])
# 4. 소수점 3자리와 스타일 적용
era_champions.style.format("{:.3f}", subset=['NA_Sales', 'JP_Sales', 'Global_Sales'])\
.background_gradient(cmap='coolwarm', subset=['Global_Sales'])
본인 참고로 키넥트도 안해봄. 집에 일단 콘솔 둘 데가 없습니다. ...팩맨이 혹시 우리가 아는 그 팩맨임?
에이, 원툴 아냐?
# 1. 진영별 전체 매출 합계
vendor_total = video_df_cp.groupby('Platform_Vendor')['Global_Sales'].sum()
# 2. 진영별 상위 10개 게임의 매출 합계
top10_sum = (video_df_cp.sort_values('Global_Sales', ascending=False)
.groupby('Platform_Vendor')
.head(10)
.groupby('Platform_Vendor')['Global_Sales'].sum())
# 3. 비중 계산 (단위: %)
dependency_df = pd.DataFrame({
'Total_Sales': vendor_total,
'Top10_Sales': top10_sum,
'Dependency_Rate(%)': (top10_sum / vendor_total) * 100
}).sort_values('Dependency_Rate(%)', ascending=False)
dependency_df.style.format("{:.3f}")
플랫폼별 일짱이 전체 매출에 얼마나 기여했는지를 보자. 위스포츠가 잘나가긴 했지만, 그럼에도 10%정도다. 마소의 헤일로2나 플스의 다른 게임들도 마찬가지.
한짤요약
# 시각화를 위한 통합 대시보드 설정
fig, axes = plt.subplots(2, 2, figsize=(20, 15))
fig.suptitle('비디오 게임 콘솔 삼국지: 데이터로 본 반전의 역사', fontsize=25, fontweight='bold')
# 1. [좌상단] 시대별 플랫폼 제조사 매출 추이 (The History)
era_revenue = video_df_cp.groupby(['Era', 'Platform_Vendor'], observed=True)['Global_Sales'].sum().unstack().fillna(0)
era_revenue.plot(kind='area', stacked=True, ax=axes[0, 0], alpha=0.7, cmap='coolwarm')
axes[0, 0].set_title('① 시대별 진영 점유율: 아타리의 몰락과 닌텐도/소니의 부상', fontsize=15)
axes[0, 0].set_ylabel('Global Sales (Millions)')
# 2. [우상단] TOP 10 의존도 비교 (The Myth Buster)
sns.barplot(x=dependency_df.index, y='Dependency_Rate(%)', data=dependency_df, hue = dependency_df.index, ax=axes[0, 1], palette='coolwarm')
axes[0, 1].set_title('② "닌텐도는 원툴?" NO! 상위 10개 타이틀 매출 비중 비교', fontsize=15)
axes[0, 1].set_ylim(0, 110) # 3DO 등 소규모 벤더 제외하고 주요 3사 강조를 위해 조정 가능
for p in axes[0, 1].patches:
axes[0, 1].annotate(f'{p.get_height():.1f}%', (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', xytext=(0, 9), textcoords='offset points')
# 3. [좌하단] 주요 진영별 국가별 판매 비중 (Region Strategy)
vendor_region = video_df_cp.groupby('Platform_Vendor')[['NA_Sales', 'EU_Sales', 'JP_Sales']].sum()
vendor_region_pct = vendor_region.div(vendor_region.sum(axis=1), axis=0) * 100
vendor_region_pct.loc[['Nintendo', 'Sony', 'Microsoft']].plot(kind='barh', stacked=True, ax=axes[1, 0], colormap='coolwarm')
axes[1, 0].set_title('③ 진영별 국가별 공략지: 북미의 MS vs 일본의 닌텐도 vs 균형의 소니', fontsize=15)
axes[1, 0].set_xlabel('Sales Percentage (%)')
# 4. [우하단] 플랫폼별 일짱 게임의 위엄 (The Champions)
sns.barplot(x='Global_Sales', y='Name', data=platform_tops.head(10), hue='Global_Sales', ax=axes[1, 1], palette='coolwarm')
axes[1, 1].set_title('④ 역대 가장 강력했던 "일짱" 타이틀 TOP 10', fontsize=15)
axes[1, 1].set_xlabel('Global Sales (Millions)')
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
1. 태초에 아타리가 있었지만 아타리 쇼크로 개같이 멸망했고, 그 자리를 닌텐도가 '엔터테인먼트 시스템'을 표방하면서 들어왔다. 그 다음에 소니가 들어와서 플.엑 양대산맥이 됐다가 삘받았는지 빌횽이 엑박을 내면서 플.엑.닌 삼국지가 된 것.
2. 위 스포츠의 판매량이 압도적인 건 인정. 하지만 닌텐도가 그걸로 원툴이었던 건 아니다.
3. 플.엑.닌은 각자의 특징을 살려서 삼국지 시장전을 하고 있다.
4. 닌텐도는 자사의 퍼스트파티들(마리오, 젤다, 동숲, 커비, 스플래툰)과 세컨트파티(포켓몬 등) 등의 IP를 내세운 독점작을 통한 이거 하려면 우리거 사세요~ 전략과 마리오 파티, 마리오 카트처럼 아이들도 즐길 수 있는 '엔터테인먼트'를 표방한 게임들을 만들었다.
5. 소니는 닌텐도보다 한 발 늦었지만, 어른이들을 위한 세련된 콘솔과 다양한 써드파티 게임들로 고객들을 끌어들였다.
6. 마소는 진입 시기가 제일 늦었던데다가 초기에 있었던 여러가지 이슈들로 인해 데이터상으로 플.엑.닌중에서 제일 판매량이 적고 일본 매출도 저조하지만, 포르자 시리즈와 헤일로, 기어스 오브 워 등의 총게임으로 북미 시장을 책임지고 있다.
수치형 데이터에 대해 describe를 쓰면 전체 데이터 개수, 평균, 표준편차, 최댓값, 최솟값, 사분위수(제 1사분위수, 제 2사분위수, 제 3사분위수)가 나온다. 근데 저게 수치형만 나오는 게 아니라 범주형이어도 값이 숫자면 다 나옴.
2. 범주형 데이터
얘는 R 서머리처럼 알아서 안 해주기때문에 범주형 데이터에 대한 요약을 보려면 include='O' 옵션을 줘야 한다.
Name Platform Genre Publisher
count 16598 16598 16598 16540
unique 11493 31 12 578
top Need for Speed: Most Wanted DS Action Electronic Arts
freq 12 2163 3316 1351
1. count: 값이 몇 개인가
2. unique: 고윳값이 몇 개인가(그니까 안 겹치는 값이요)
3. top: 최빈값
4. freq: 빈도(아마도 최빈값 빈도?)
df.isna().sum()
age 0
anaemia 0
creatinine_phosphokinase 0
diabetes 0
ejection_fraction 0
high_blood_pressure 0
platelets 0
serum_creatinine 0
serum_sodium 0
sex 0
smoking 0
time 0
DEATH_EVENT 0
dtype: int64