denisbruno commited on
Commit
2c7f1a3
·
verified ·
1 Parent(s): a4e7225

Upload 27 files

Browse files
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Python 3",
3
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
4
+ "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
5
+ "customizations": {
6
+ "codespaces": {
7
+ "openFiles": [
8
+ "README.md",
9
+ "app.py"
10
+ ]
11
+ },
12
+ "vscode": {
13
+ "settings": {},
14
+ "extensions": [
15
+ "ms-python.python",
16
+ "ms-python.vscode-pylance"
17
+ ]
18
+ }
19
+ },
20
+ "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
21
+ "postAttachCommand": {
22
+ "server": "streamlit run app.py --server.enableCORS false --server.enableXsrfProtection false"
23
+ },
24
+ "portsAttributes": {
25
+ "8501": {
26
+ "label": "Application",
27
+ "onAutoForward": "openPreview"
28
+ }
29
+ },
30
+ "forwardPorts": [
31
+ 8501
32
+ ]
33
+ }
.dockerignore ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ **/__pycache__
2
+ **/.venv
3
+ **/.classpath
4
+ **/.dockerignore
5
+ **/.env
6
+ **/.git
7
+ **/.gitignore
8
+ **/.project
9
+ **/.settings
10
+ **/.toolstarget
11
+ **/.vs
12
+ **/.vscode
13
+ **/.devcontainer
14
+ **/*.*proj.user
15
+ **/*.dbmdl
16
+ **/*.jfm
17
+ **/bin
18
+ **/charts
19
+ **/docker-compose*
20
+ **/compose*
21
+ **/Dockerfile*
22
+ **/node_modules
23
+ **/npm-debug.log
24
+ **/obj
25
+ **/secrets.dev.yaml
26
+ **/values.dev.yaml
27
+ **/notebooks
28
+ LICENSE
29
+ README.md
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/raw/500+_raw.xlsx filter=lfs diff=lfs merge=lfs -text
37
+ resources/logo.png filter=lfs diff=lfs merge=lfs -text
38
+ resources/mapa_calor_musicas.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ #.idea/
Dockerfile CHANGED
@@ -1,20 +1,24 @@
1
- FROM python:3.13.5-slim
2
-
3
- WORKDIR /app
4
-
5
- RUN apt-get update && apt-get install -y \
6
- build-essential \
7
- curl \
8
- git \
9
- && rm -rf /var/lib/apt/lists/*
10
-
11
- COPY requirements.txt ./
12
- COPY src/ ./src/
13
-
14
- RUN pip3 install -r requirements.txt
15
-
16
- EXPOSE 8501
17
-
18
- HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
-
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
 
 
 
 
1
+ # For more information, please refer to https://aka.ms/vscode-docker-python
2
+ FROM python:3.10-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy and install system dependencies from packages.txt
8
+ COPY packages.txt .
9
+ RUN apt-get update \
10
+ && apt-get install -y --no-install-recommends locales-all ffmpeg git \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Copy requirements and install
14
+ COPY requirements.txt .
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copy all project files
18
+ COPY . .
19
+
20
+ # Expose Streamlit default port
21
+ EXPOSE 80
22
+
23
+ # Run the Streamlit app
24
+ CMD ["streamlit", "run", "app.py", "--server.port=80", "--server.address=0.0.0.0"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Denis Bruno
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,20 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Teste
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
- pinned: false
11
- short_description: Streamlit template space
12
- license: mit
13
- ---
14
 
15
- # Welcome to Streamlit!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
18
 
19
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
20
- forums](https://discuss.streamlit.io).
 
 
 
 
1
+ # 500mais-kissfm
2
+ Projeto de DataScience da lista das 500 mais da Kiss FM.
3
+
4
+ A aplicação interativa com todas as análises aqui descritas está disponível em [https://500maiskissfm.streamlit.app/](https://500maiskissfm.streamlit.app/)
5
+
6
+ # Tratamento de dados
7
+
8
+ Antes da análise dos dados relativos à todos os anos das 500+ da Kiss FM foi necessário agrega-los. Os itens a seguir descrevem todo esse processo.
9
+ A maior parte dele foi automatizada, porém em alguns momentos uma intervenção manual foi necessária. Dessa forma, eventuais erros podem ter sido introduzidos na listagem, já que a conferência final foi feita por amostragem. Assim sendo, reporte qualquer inconsistência por meio das issues do repositório.
10
+
11
+ ## Ferramentas utitlizadas
12
+
13
+ * Microsoft Excel
14
+ * OpenRefine 3.7.7
15
+ * Wikidata Reconciliation Service
16
+ * Wikidata API
17
+
18
+ Projeto do OpenRefine está disponível neste [arquivo](../main/data/500%2B_openrefine.tar.gz).
19
+
20
+ ## Coleta
21
+
22
+ Os dados foram coletados a partir das <a href="#fontes">fontes</a> listadas, na ordem em que foram elencadas. Em alguns casos foi realizada uma referência cruzada das informações das fontes para tirar dúvidas sobre as informações das músicas, posições, álbuns, etc.
23
+ Nesta etapa todo o trabalho foi realizado utilizando o Excel e o resultado final foi compilado no [arquivo raw](../main/data/raw/500%2B_raw.xlsx).
24
+ Após isso foi gerada uma planilha com todas as 12.000 músicas (até o momento, compreendendo os anos 2000-2023) para início do processo de tratamento.
25
+
26
+ ## Tratamento e clusterização
27
+
28
+ Para a limpeza e tratamento dos dados a planilha com todas as músicas foi importada no OpenRefine. A partir daí, algumas adequações e ajustes foram necessários para padronizar os dados, descritos a seguir.
29
+
30
+ A padronização e unificação dos nomes foi realizado por meio dos métodos de [clustering do OpenRefine](https://openrefine.org/docs/technical-reference/clustering-in-depth). Foram utilizados diversos métodos até que fosse possível ter o mínimo possível de duplicatas. Ainda assim, alguma revisão manual foi realizada.
31
+
32
+ ### Posição
33
+
34
+ O tratamento dos dados de posição das <a href="#outras-considerações">músicas não identificadas</a> precisou de cruzamento das listagens das 500+ de algumas fontes. Em caso de conflitos entre 2 listas diferentes, optou-se por permanecer com os dados da lista mais recente.
35
+
36
+ ### Artista
37
+
38
+ Os nomes dos artistas e bandas foram padronizados, agregando itens que estavam com diversos formatos (maiúsculo, minúsculo, com erros de grafia, etc.). Após isto foram analisados os casos específicos de colaboração, participação e junção de diferentes artistas em uma música.
39
+
40
+ Para as colaborações optou-se por manter a junção por meio de conectivos, tais como "and", "&" ou "e". Isto foi orientado por uma rápida pesquisa na Wikipedia.
41
+
42
+ As participações (conhecidas como "featuring") foram removidas do nome do artista e registradas em um novo campo "Observacao" no arquivo final (também utilizado para informações da música em si).
43
+
44
+ ### Música
45
+
46
+ Os nomes das músicas também foram padronizados, agregando itens que estavam com diversos formatos (maiúsculo, minúsculo, com erros de grafia, etc.). Após isto foram analisados os casos específicos de "tipos" de músicas.
47
+
48
+ Diversas observações foram realizadas para esses "tipos" de músicas, tais como "ao vivo", "acústica", ou alguma outra versão específica. Para músicas "ao vivo" e "acústica" em específico, essa observação norteou a escolha do álbum (descrito na próxima seção).
49
+
50
+ Além disso, foi registrado também se a música foi repetida na lista daquele ano (ocorreram 32 vezes ao longo desses 24 anos). Nas fontes consultadas já havia a indicação de que algumas músicas estavam repetidas, o que leva a crer que não foi um erro de digitação de quem registrou a informação, mas sim uma falha na programação da própria Kiss. Neste caso, foi anotado no campo "observação" como "repetida", na música da posição mais baixa (ou seja, quando ela efetivamente foi repedita na reprodução).
51
+
52
+ ### Álbum/Single
53
+
54
+ Optou-se pela escolha não somente de álbuns, mas também de singles como obra principal em que a música aparece. Assim sendo, a obra com data de lançamento mais antiga foi escolhida.
55
+
56
+ No caso de músicas com observações, tais como "ao vivo" e "acústica" foi escolhido o primeiro álbum/single em que uma versão assim aparece.
57
+
58
+ Como fonte de informação para checagem manual das datas de lançamento do álbum/single, o seguintes sites foram consultados, prevalecendo também a data mais antiga de lançamento encontrada:
59
+
60
+ 1. Wikipedia
61
+ 2. Rate Your Music
62
+ 3. Discogs
63
+
64
+ Em alguns casos, apenas o ano (ou mês e ano) do lançamento foi identificado. Dessa forma, o registro foi marcado com o dia 01 de janeiro (ou do mês indicado) do respectivo ano. Ex:
65
+
66
+ * Álbum lançado em ??/??/1956 -> Data de lançamento 01/01/1956
67
+ * Álbum lançado em ??/12/1956 -> Data de lançamento 01/12/1956
68
+
69
+ Todos os ��lbuns têm pelo menos o ano preenchido corretamente.
70
+
71
+ Considerando que neste momento a análise dos dados neste projeto de DataScience será focada somente no ano, não haverá prejuízo. A busca por essa informação seguirá em andamento para refinar os dados.
72
+
73
+ ### País
74
+
75
+ O país de origem dos artistas foi incluído com base em alguns critérios. O primeiro, a partir da reconciliação automática dos dados da Wikidata (explicado a seguir). O segundo, para o caso das bandas, prevaleceu o local de formação da mesma. No caso de artistas solo, o local de nascimento.
76
+
77
+ Para músicas com colaborações e/ou participações, prevaleceu o país do artista principal.
78
+
79
+ Por fim, optou-se por considerar todas as músicas pertencentes ao Reino Unido como Reino Unido em si mesmo, em vez de separar em Inglaterra, Escócia, Irlanda do Norte e País de Gales. Essa decisão foi tomada com base na análise de outros rankings de músicas que costumam considerar dessa forma (o dado original foi mantido na coluna "Country" do dataset).
80
+
81
+
82
+ ### Gênero Musical
83
+
84
+ Enquadrar um artista em um único gênero musical é difícil pois a grande maioria não produz músicas em um único estilo, e esse estilo pode variar ao longo da carreira. Além disso, muitas variações de gêneros e sub-gêneros musicais podem gerar confusão. Entretanto, para esta análise uma classificação mínima foi necessária.
85
+
86
+ Para isso, foi feito um cruazamento entre várias bases de dados de artistas de modo a tentar elencar o principal gênero musical:
87
+
88
+ * Wikipédia
89
+ * MusicBrainz
90
+ * Last.fm
91
+ * The Audio DB
92
+ * Rate Your Music
93
+ * Chosic
94
+ * AllMusic
95
+
96
+ Com base nos gêneros listados para um determinado artista nestas fontes, tentou-se encontrar o gênero musical que se repetiu mais entre todos, sendo este então atribuído ao artista.
97
+
98
+ Após isso, os gêneros foram traduzidos para o português seguindo o que consta nas respectivas entradas na Wikipédia.
99
+
100
+ ### Duração
101
+
102
+ A duração das músicas foi obtida a partir das músicas reconciliadas via OpenRefine e também por meio de web scrapping das respectivas páginas da Wikipédia (o notebook utilizado encontra-se no projeto).
103
+
104
+ Para aquelas em que os dados não foram encontradas com os métodos acima, a duração foi localizada manualmente nas bibliotecas do Rate Your Music e Discogs.
105
+
106
+ A duração indicada pode não ser precisa, uma vez que durante a programação das 500+ pode ser tido alguma variação de versão da música.
107
+
108
+
109
+ ## Reconciliação (via Wikidata)
110
+
111
+ Boa parte do processo de tratamento de dados foi automatizado por meio da funcionalidade de [reconciliação](https://openrefine.org/docs/manual/reconciling) do OpenRefine e o respectivo serviços da Wikidata.
112
+
113
+ Ao reconciliar os dados de artistas, músicas e álbuns com dados da Wikipedia foi possível validar as informações com confiabilidade. A reconciliação automática obteve os seguintes resultados:
114
+ * Artistas: 98,33%
115
+ * Músicas: 90%
116
+ * Álbuns/Singles: 95%
117
+
118
+ No caso dos Artistas, aqueles que possuem algum tipo de colaboração nas músicas não foi possível reconciliar. Para músicas e álbuns/singles, uma revisão manual foi realizada, elevando os índices de reconciliação para 94% e 98%, respectivamente.
119
+
120
+
121
+ ## Outras Considerações
122
+
123
+ Após todo o trabalho, ainda restaram 11 músicas não identificadas nas fontes (2013: 1, 2007: 9, 2006: 1). Em duas delas há pelo menos a indicação dos artistas. Mesmo assim, pode haver erros no registro desses 2 casos. As músicas não identificadas são as seguintes:
124
+
125
+ |Ano|Posição|Artista|Música|
126
+ |:----|:----|:----|:----|
127
+ |2013|311|?|?|
128
+ |2007|49|?|?|
129
+ |2007|54|?|?|
130
+ |2007|97|?|?|
131
+ |2007|262|?|?|
132
+ |2007|265|?|?|
133
+ |2007|266|?|?|
134
+ |2007|267|?|?|
135
+ |2007|269|?|?|
136
+ |2007|304|Bad Company|?|
137
+ |2006|471|Alice in Chains|?|
138
+
139
+ Caso você consiga essa informação, por favor abra uma issue para que o arquivo possa ser complementado.
140
+
141
+ Em um projeto complementar tentarei estimar quais eram as possíveis músicas, com base na análise dos dados das músicas votadas em outros anos.
142
+
143
+
144
+ **EDIT**: Graças à uma planilha cedida pelo [@humbertobiasin](https://x.com/humbertobiasin) pude preencher a lista dessas músicas não identificadas. Foram elas:
145
+
146
+ * 2006: #471 - No Excuses (Alice in Chains)
147
+ * 2007: #49 - Somebody to Love (Queen); #54 - We Are the Champions (Queen); #97 - Boys Don't Cry (The Cure); # 262 - Sister (The Nixons); #265 - Red Sector A (Rush); #266 - Runnin' With the Devil (Van Halen); #267 - The Long and Winding Road (The Beatles); 269 - Breakfast at Tiffany's (Deep Blue Something); #304 Only Time Will Tell (Asia) (A não identificada do Bad Company era Can't Get Enough e ficou na posição #303)
148
+ * 2013: #311 - Sad but True (Metallica)
149
  ---
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ # Análise dos dados
152
+
153
+ Após o correto tratamento dos dados, algumas análises foram realizadas. Além daquelas aqui descritas, todas as demais estarão na aplicação.
154
+
155
+ ## Ferramentas
156
+
157
+ Foram utilizadas as seguintes ferramentas para analisar os dados:
158
+
159
+ * Python 3.10.13
160
+ * pandas 2.2.0
161
+ * NumPy 1.24.4
162
+ * Matplotlib 3.7.4
163
+ * seaborn 0.12.2
164
+
165
+ ## Considerações
166
+
167
+ Para a análise foram desconsideradas as músicas e artistas em branco, que representam as <a href="#outras-considerações">músicas não identificadas</a>. Também foram desconsideradas as músicas anotadas como repetidas. Músicas ao vivo e acústicas foram consideradas como diferentes.
168
+
169
+ ### O caso de Another Brick in the Wall
170
+
171
+ A música "Another Brick in the Wall", do Pink Floyd, é um caso a parte a ser discutido.
172
+
173
+ Em primeiro lugar, a música é dividida em 3 partes, que podem ser tocadas juntas ou em separado, sendo a parte 2 a mais conhecida. Nos dados obtidos durante a etapa de tratamento, em alguns anos havia a informação de qual parte foi tocado, porém em outros não. Neste caso, quando não foi mencionada a parte foi considerada que o que foi tocado é o conjunto das 3 partes.
174
+
175
+ Em segundo lugar, ainda na execução das 3 partes há a possibilidade da execução da música "The Happiest Days of Our Lives", que antecede a parte 2. De novo, em alguns anos há a indicação de que essa música foi tocada junto com a parte 2, e em outros com todas as partes.
176
+
177
+ Neste sentido, optou-se por considerar cada execução como uma música distinta. Isto, por sua vez, gera uma distorção nas análises, uma vez que faz com que "Another Brick in the Wall" não apareça como uma música presente em todos os anos, mas sim alguma de suas partes.
178
+
179
+ Uma forma de corrigir essa distorção é tratar todas elas como se fosse uma só, por meio do seguinte código:
180
+
181
+ ```
182
+ df.loc[df['Musica'].str.contains('Another Brick', na=False), 'Musica'] = 'Another Brick in the Wall'
183
+ ```
184
+
185
+ ### A edição de 23/24
186
+
187
+ Na edição de 23/24 a Kiss integrou na listagem das 500+ pela primeira vez músicas em português, o que gerou um grande outlier na amostragem de músicas e algumas distorções (por exemplo). Para uma análise mais precisa o ideal seria ter desconsiderado retirar este ano, porém não foi realizado.
188
+
189
+ ## Análises
190
+
191
+ O primeiro resultado interessante da análise é ver a evolução da quantidade de músicas distintas que foram sendo incorporados a cada novo ano, chegando a um total de 2163 músicas. Uma crescente maior nos primeiros anos (por razões óbvias) e se estabilizando ao longo do tempo, voltando a crescer novamente em 23/24, pelas razões já <a href="#o-ano-de-2324">explicadas anteriormente</a>.
192
+
193
+ ![Evolução de músicas distintas ao longo dos anos](./resources/evolucao_musicas.png "Evolução de músicas distintas ao longo dos anos")
194
+
195
+ Outra análise é ver a quantidade de músicas distintas por década de seu lançamento. Como esperado, há um domínio de músicas das décadas de 70 e 80 (com a década de 70 ligeiramente à frente), uma vez que foram os anos de ouro do classic rock.
196
+
197
+ ![Contagem de músicas por décadas de lançamento](./resources/musicas_decadas.png "Contagem de músicas por décadas de lançamento")
198
+
199
+ De todas as 2163 músicas até agora já tocadas na programação das 500+ nesses 24 anos, interessante notar que apenas 15 delas estiveram presentes em todas as edições (sem contar <a href="#o-caso-de-another-brick-in-the-wall">"Another Brick in the Wall"</a>), o que representa 0,69%.
200
+
201
+ Entre essas, as maiores vencedoras das 500+, "Stairway to Heaven" e "Bohemian Rhapsody" acumulam 16 vitórias, sendo 10 para a primeira e 6 para a segunda.
202
+
203
+ ![Mapa de calor de músicas que apareceram em todas as edições](./resources/mapa_calor_musicas.png "Mapa de calor de músicas que apareceram em todas as edições")
204
+
205
+ Nenhuma música conquistou um "tricampeonato". "Stairway to Heaven" tem 4 bicampeonatos.
206
+
207
+ ## As maiores de todos os tempos
208
+
209
+ Por fim, a análise mais interessante, e que foi o principal motivador desse projeto, foi descobrir quais são as maiores de todos os tempos das 500+!
210
+
211
+ Para isso foi empregada uma [Média Bayesiana](https://en.wikipedia.org/wiki/Bayesian_average) de todas as posições de uma determinada música. A média bayesiana ajuda a eliminar distorções de músicas que aparecem pouquíssimas vezes (1 ou 2) em posições melhores, com pesos atribuídos em função da quantidade de aparições. Esse é um cálculo muito utilizado em tratamento de rankings de produtos e itens a partir de avaliações de usuários, como por exemplo no IMDb e Amazon. Este [artigo](https://arpitbhayani.me/blogs/bayesian-average/) explica bem o conceito.
212
+
213
+ São elas:
214
+
215
+ |Posição|Artista|Música|
216
+ |:----|:----|:----|
217
+ |1|Queen|Bohemian Rhapsody|
218
+ |2|Led Zeppelin|Stairway to Heaven|
219
+ |3|Deep Purple|Smoke on the Water|
220
+ |4|Led Zeppelin|Kashmir|
221
+ |5|Eagles|Hotel California|
222
+ |6|The Beatles|Help!|
223
+ |7|AC/DC|Back in Black|
224
+ |8|Iron Maiden|Fear of the Dark|
225
+ |9|The Rolling Stones|(I Can't Get No) Satisfaction|
226
+ |10|Dire Straits|Sultans of Swing|
227
+
228
+ Em função de ter ficado em 27ª em 23/24, "Stairway to Heaven" acaba ficando em segundo lugar, mesmo tendo vencido em 10 anos de votação. Uma posição a menos (26ª) e já seria um empate. Novamente, a edição 23/24 acabou gerando uma distorção.
229
 
230
+ ## Fontes
231
 
232
+ 1. Planilha compilada pelo [@fabriciorby](https://x.com/fabriciorby) - https://docs.google.com/spreadsheets/d/1OHdR-RKBsELOR5nZ-L5pa8OohbvdNT29z7T-6SfWD70/
233
+ 2. Planilha compilada gentilmente cedida pelo [@humbertobiasin](https://x.com/humbertobiasin)
234
+ 3. Blog "LISTA das 500 MAIS da KISS FM de 2000 a 2023" - https://leitespc.blogspot.com/
235
+ 4. Blog "Álbuns de Cabeceira" - https://albunsdecabeceira.blogspot.com/
236
+ 5. Site Whiplash - https://whiplash.net/materias/melhores/195761.html, https://whiplash.net/materias/melhores/170703.html
app.py ADDED
@@ -0,0 +1,468 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import base64
3
+ import core_functions as core
4
+ import charts as ch
5
+ import components as components
6
+ from info import InfoEdicao, InfoMusica, InfoArtista, InfoCuriosidade
7
+ import locale
8
+ import streamlit as st
9
+ from streamlit_timeline import timeline
10
+
11
+ #Configuração
12
+ locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
13
+ logo_file = './resources/logo.png'
14
+ icon_file = './resources/favicon.ico'
15
+ versao = '1.4.0'
16
+
17
+ def configurar_css():
18
+ st.markdown(
19
+ """
20
+ <style>
21
+ [data-testid='stMetricDeltaIcon-Up'] {
22
+ display: none;
23
+ }
24
+ </style>
25
+ """,
26
+ unsafe_allow_html=True,
27
+ )
28
+
29
+ def plotar_grafico(fig):
30
+ st.plotly_chart(fig, use_container_width=True)
31
+
32
+ def plotar_mapa_calor(fig):
33
+ config = {'scrollZoom': False,
34
+ 'modeBarButtonsToRemove': [
35
+ 'zoom', 'pan', 'select', 'zoomIn', 'zoomOut', 'autoScale', 'resetScale']}
36
+
37
+ st.plotly_chart(fig, use_container_width=True, config = config)
38
+
39
+ def plotar_timeline(edicao):
40
+ items = json.loads(edicao.get_musicas())
41
+
42
+ options = {
43
+ "start_at_end": False,
44
+ "timenav_height": 50,
45
+ "is_embed": True,
46
+ "scale_factor": 11,
47
+ "duration": 300,
48
+ "language": "pt-br"
49
+ }
50
+
51
+ timeline(items, height=400, additional_options=options)
52
+
53
+ @st.cache_resource(show_spinner='Gerando gráfico de corrida...')
54
+ def plotar_grafico_race(df_data, atributo, titulo):
55
+ html_data = ch.gerar_grafico_race(df_data, atributo, titulo)
56
+
57
+ start = html_data.find('base64,') + len('base64,')
58
+ end = html_data.find('">')
59
+
60
+ video = base64.b64decode(html_data[start:end])
61
+ st.video(video)
62
+
63
+ @st.cache_data
64
+ def load_data(agregar_pinkfloyd):
65
+ return core.load_data(agregar_pinkfloyd)
66
+
67
+ @st.cache_data
68
+ def load_predicoes():
69
+ return core.load_predicoes()
70
+
71
+ @st.cache_data
72
+ def get_dicionario_musicas(df_data):
73
+ return core.get_dicionario_musicas(df_data)
74
+
75
+ @st.cache_data
76
+ def get_dicionario_artistas(df_data):
77
+ return core.get_dicionario_artistas(df_data)
78
+
79
+ @st.cache_data
80
+ def show_data(df_data):
81
+ st.dataframe(data=df_data.reset_index(drop=True), hide_index=True)
82
+
83
+ #App
84
+ st.set_page_config(layout="wide",
85
+ page_title='As 500+ da Kiss',
86
+ page_icon=icon_file,
87
+ menu_items={
88
+ 'Get Help': 'https://github.com/denisvirissimo/500mais-kissfm',
89
+ 'Report a bug': "https://github.com/denisvirissimo/500mais-kissfm/issues",
90
+ 'About': '''Desenvolvido por [Denis Bruno Viríssimo](https://www.linkedin.com/in/denisbruno/)
91
+ Versão {}'''.format(versao)
92
+ })
93
+
94
+ configurar_css()
95
+
96
+ if 'opt_pink_floyd' not in st.session_state:
97
+ st.session_state.opt_pink_floyd = False
98
+
99
+ df_listagem = load_data(st.session_state.opt_pink_floyd)
100
+ df_predicoes = load_predicoes()
101
+
102
+ list_analises_edicao = {"Músicas por Artista":'Musica_Artista', "Álbuns por Artista":'Album_Artista', "Músicas por Gênero":'Musica_Genero', "Gêneros por País":'Genero_Pais', "Duração":'Duracao'}
103
+ list_variaveis_topn = {"Artista": 'Artista', "Música": 'Musica', "Álbum/Single": 'Album', "Gênero": 'Genero', "Artistas com músicas em posições similares": 'Artista_Posicao'}
104
+ medidas = ["Média", "Mediana", "Máximo"]
105
+
106
+
107
+ #Sidebar
108
+ st.sidebar.subheader('Filtros')
109
+ st.sidebar.text('')
110
+
111
+ #Filtro Edições
112
+ edicoes = core.listar_edicoes(df_listagem)
113
+ edicao_inicial, edicao_final = st.sidebar.select_slider('Filtrar por edições', edicoes, value = [core.get_primeira_edicao(df_listagem).values[0], core.get_ultima_edicao(df_listagem).values[0]])
114
+ df_listagem_filtrada = core.filtrar_edicao(df_listagem, edicao_inicial, edicao_final)
115
+
116
+ #Filtro Posições
117
+ posicoes = core.listar_posicoes(df_listagem)
118
+ posicao_inicial, posicao_final = st.sidebar.select_slider('Filtrar por posições', posicoes, value=[min(posicoes), max(posicoes)])
119
+ df_listagem_filtrada = core.filtrar_posicoes(df_listagem_filtrada, posicao_inicial, posicao_final)
120
+
121
+ #Filtro Ano Lançamento
122
+ anos = core.listar_anos_lancamento(df_listagem)
123
+ ano_inicial, ano_final = st.sidebar.select_slider('Filtrar por anos de lançamento das músicas', anos, value=[min(anos), max(anos)])
124
+ df_listagem_filtrada = core.filtrar_anos(df_listagem_filtrada, ano_inicial, ano_final)
125
+
126
+ st.sidebar.caption('Estes filtros se aplicam somente às abas Visão Geral e Análises.')
127
+
128
+ st.sidebar.subheader('Opções')
129
+
130
+ st.sidebar.toggle('Agregar múltiplas versões de Another Brick in the Wall', key='opt_pink_floyd', help='[Clique aqui](https://github.com/denisvirissimo/500mais-kissfm#o-caso-de-another-brick-in-the-wall) para entender.')
131
+
132
+ col1, col2, col3 = st.columns((.2, 7.1, .2))
133
+
134
+ with col2:
135
+ row_titulo_col1, row_titulo_col2 = st.columns((.25, 3.3), gap="small")
136
+ with row_titulo_col1:
137
+ st.image(logo_file, width=75)
138
+ with row_titulo_col2:
139
+ st.title('As 500+ da Kiss FM')
140
+
141
+ st.markdown("Esse é um projeto de Ciência de Dados com o objetivo de analisar a listagem das 500+ da rádio Kiss FM. A ideia surgiu a partir da curiosidade de saber qual seria a música número 1 de todas as votações até então, e acabou levando ao desenvolvimento de várias outras análises interessantes.")
142
+ st.markdown("Todo o detalhamento do projeto, inclusive o tratamento de dados e algumas curiosidades, está disponível neste [repositório do GitHub](https://github.com/denisvirissimo/500mais-kissfm)")
143
+
144
+ st.markdown("")
145
+ with st.status("Carregando...") as status:
146
+ show_data(df_listagem)
147
+ status.update(label="Clique aqui para ver a listagem completa", state="complete")
148
+
149
+ st.text('')
150
+ st.subheader("Exibindo os seguintes dados a partir dos filtros:")
151
+
152
+ row_numeros_col1, row_numeros_col2, row_numeros_col3, row_numeros_col4, row_numeros_col5, row_numeros_col6, row_numeros_col7 = st.columns((1.6, 1.6, 1.0, 1.5, 1.6, 1.4, 1.1), gap="small")
153
+ with row_numeros_col1:
154
+ total_musicas = df_listagem_filtrada.Id.nunique()
155
+ str_total_musicas = "🎶 {} músicas no total".format(locale.format_string("%d", total_musicas, grouping = True))
156
+ st.markdown(str_total_musicas)
157
+ with row_numeros_col2:
158
+ total_musicas_distintas = core.get_total_musicas_distintas(df_listagem_filtrada)
159
+ str_total_musicas_distintas = "🎵 {} músicas diferentes".format(locale.format_string("%d", total_musicas_distintas, grouping = True))
160
+ st.markdown(str_total_musicas_distintas)
161
+ with row_numeros_col3:
162
+ total_artistas = core.get_total_artistas_distintos(df_listagem_filtrada)
163
+ str_total_artistas = "👨🏽‍🎤 {} artista(s)".format(locale.format_string("%d", total_artistas, grouping = True))
164
+ st.markdown(str_total_artistas)
165
+ with row_numeros_col4:
166
+ total_albuns = core.get_total_albuns_distintos(df_listagem_filtrada)
167
+ str_total_albuns = "💿 {} álbum(s)/single(s)".format(locale.format_string("%d", total_albuns, grouping = True))
168
+ st.markdown(str_total_albuns)
169
+ with row_numeros_col5:
170
+ total_paises = core.get_total_paises_distintos(df_listagem_filtrada)
171
+ str_total_paises = "🌎 {} países representados".format(locale.format_string("%d", total_paises, grouping = True))
172
+ st.markdown(str_total_paises)
173
+ with row_numeros_col6:
174
+ total_generos = core.get_total_generos_distintos(df_listagem_filtrada)
175
+ str_total_generos = "🤘 {} gêneros musicais".format(locale.format_string("%d", total_generos, grouping = True))
176
+ st.markdown(str_total_generos)
177
+ with row_numeros_col7:
178
+ total_horas = core.get_total_horas(df_listagem_filtrada)
179
+ str_total_horas = "🕛 {}+ horas".format(locale.format_string("%d", total_horas, grouping = True))
180
+ st.markdown(str_total_horas)
181
+
182
+ st.divider()
183
+
184
+ tab_geral, tab_edicao, tab_edicoes, tab_analises, tab_curiosidades, tab_predicoes = st.tabs(["Visão Geral", "Por Edição", "Todas as Edições", "Análises", "Curiosidades", "Predições"])
185
+
186
+ with tab_geral:
187
+ st.subheader('Evolução de músicas distintas ao longo dos anos')
188
+ plotar_grafico(ch.get_grafico_barra(core.get_acumulado_musicas_distintas(df_listagem_filtrada), "Anos", "Acumulado", "Edições", "Acumulado de Músicas distintas"))
189
+
190
+ st.divider()
191
+
192
+ st.subheader('Evolução de gêneros musicais distintos ao longo dos anos')
193
+ plotar_grafico(ch.get_grafico_barra(core.get_acumulado_generos_distintos(df_listagem_filtrada), "Anos", "Acumulado", "Edições", "Acumulado de Gêneros Musicais distintos"))
194
+
195
+ st.divider()
196
+
197
+ st.subheader('Artistas, Músicas, Álbuns e Gêneros no Topo')
198
+
199
+ row_topn_col1, row_topn_col2 = st.columns((2, 5), gap="large")
200
+ with row_topn_col1:
201
+ top_n = st.slider('Qual Top N você deseja visualizar?', 1, 50, 3)
202
+ variavel_topn_selecionada = st.selectbox ("Escolha a variável para visualizar no Top", list(list_variaveis_topn.keys()), key = 'variavel_topn')
203
+ if (list_variaveis_topn[variavel_topn_selecionada] == 'Artista_Posicao'):
204
+ st.caption('Considera-se música em posição similar aquela com uma variação de até 5 posições (para mais ou para menos)')
205
+ with row_topn_col2:
206
+ match list_variaveis_topn[variavel_topn_selecionada]:
207
+ case 'Artista':
208
+ st.dataframe(data=core.get_artistas_top_n(df_listagem_filtrada, top_n), hide_index=True, use_container_width=True, height=400, column_config={"Artista":"Artista", "Total_Aparicoes": "Número Total de Aparições"})
209
+ case 'Musica':
210
+ st.dataframe(data=core.get_musicas_top_n(df_listagem_filtrada, top_n), hide_index=True, use_container_width=True, height=400, column_config={"Musica":"Música", "Total_Aparicoes": "Número Total de Aparições"})
211
+ case 'Album':
212
+ st.dataframe(data=core.get_albuns_top_n(df_listagem_filtrada, top_n), hide_index=True, use_container_width=True, height=400, column_config={"Album_Single":"Álbum/Single", "Total_Aparicoes": "Número Total de Aparições"})
213
+ case 'Genero':
214
+ st.dataframe(data=core.get_generos_top_n(df_listagem_filtrada, top_n), hide_index=True, use_container_width=True, height=400, column_config={"Genero":"Gênero", "Total_Aparicoes": "Número Total de Aparições"})
215
+ case 'Artista_Posicao':
216
+ st.dataframe(data=core.get_artistas_posicoes_semelhantes_top_n(df_listagem_filtrada, top_n), hide_index=True, use_container_width=True, height=400, column_config={"Artista": "Artista", "Posicao_Semelhante": st.column_config.NumberColumn("Porcentagem de vezes em posições similares", format="percent")})
217
+ case default:
218
+ st.write('Escolha uma opção')
219
+
220
+ st.divider()
221
+
222
+ st.subheader('Músicas distintas por Ano de Lançamento')
223
+ plotar_grafico(ch.get_grafico_barra(core.get_musicas_ano_lancamento(df_listagem_filtrada), "Data_Lancamento_Album", "Total_Musicas", "Anos", "Quantidade de Músicas distintas", True))
224
+
225
+ st.divider()
226
+
227
+ st.subheader('Músicas distintas por Década de Lançamento')
228
+ plotar_grafico(ch.get_grafico_barra(core.get_musicas_decada_lancamento(df_listagem_filtrada), "Decada_Lancamento_Album", "Total_Musicas", "Décadas", "Quantidade de Músicas distintas"))
229
+
230
+ st.divider()
231
+
232
+ st.subheader('Músicas distintas por País do Artista')
233
+ plotar_grafico(ch.get_grafico_barra_stacked(core.get_musicas_por_pais(df_listagem_filtrada), "Edicao", "Total_Musicas", "Pais", "Edições", "Músicas por País", "Países"))
234
+
235
+ st.divider()
236
+
237
+ st.subheader('Músicas distintas por Gênero Musical do Artista')
238
+ plotar_grafico(ch.get_grafico_barra_stacked(core.get_musicas_por_genero(df_listagem_filtrada), "Edicao", "Total_Musicas", "Genero", "Edições", "Músicas por Gênero Musical", "Gêneros Musicais"))
239
+
240
+ st.divider()
241
+
242
+ row_posicaogenero, row_paises = st.columns((3.5, 3.5), gap="large")
243
+ with row_posicaogenero:
244
+ st.subheader('Melhor posição de cada gênero')
245
+ st.dataframe(data=core.get_melhor_posicao_genero(df_listagem_filtrada), hide_index=True, use_container_width=True, height=400, column_config={"Genero":"Gênero", "Posicao": "Melhor Posição", "Edicao": "Edição"})
246
+
247
+ with row_paises:
248
+ st.subheader('Mapa de Países')
249
+ plotar_grafico(ch.get_mapa(core.get_musicas_por_pais(df_listagem_filtrada, True), "Country", "Total_Musicas", "Pais", "Quantidade de Músicas"))
250
+
251
+ with tab_edicao:
252
+
253
+ st.markdown('Escolha uma edição e veja algumas informações relavantes:')
254
+
255
+ row_edicoes_col1, row_edicoes_col2= st.columns((1.5, 6.2), gap="small")
256
+ with row_edicoes_col1:
257
+ anos = core.listar_anos_edicoes(df_listagem)
258
+ list_edicoes = dict(zip(edicoes, anos))
259
+ edicao_selecionada = st.selectbox ("Edição", list_edicoes.keys(), key = 'edicao_selecionada')
260
+
261
+ ano_edicao = list_edicoes[edicao_selecionada]
262
+ info_edicao = InfoEdicao(df_listagem, ano_edicao)
263
+ st.divider()
264
+
265
+ st.subheader("Linha do tempo das músicas na edição")
266
+ plotar_timeline(info_edicao)
267
+
268
+ st.caption('Use os as setas ao lado para avançar/retornar na linha do tempo. Clique e arraste na linha para avançar um período maior.')
269
+
270
+ st.divider()
271
+ row_dadosedicao_col1, row_dadosedicao_col2, row_dadosedicao_col3 = st.columns((1.2, 2.6, 2.6), gap="large")
272
+
273
+ with row_dadosedicao_col1:
274
+ st.subheader('Dados Gerais')
275
+
276
+
277
+ st.markdown('Neste ano a 1ª posição ficou com **{}** e a posição de número 500 com **{}**.'.format(info_edicao.get_musica_posicao(1), info_edicao.get_musica_posicao(500)))
278
+
279
+ st.markdown('O Artista em que mais apareceu na listagem foi **{}**.'.format(info_edicao.get_top_artista()))
280
+ st.markdown('Já o Álbum/Single com mais músicas na lista foi **{}**.'.format(info_edicao.get_top_album()))
281
+
282
+ st.markdown('O Gênero Musical mais tocado foi **{}**.'.format(info_edicao.get_top_genero()))
283
+
284
+ st.markdown('A Música de menor duração foi **{}** e a música de maior duração foi **{}**'.format(info_edicao.get_musica_menor_duracao(), info_edicao.get_musica_maior_duracao()))
285
+
286
+ st.markdown('E tivemos música repetida? **{}**!'.format(info_edicao.get_repetidas()))
287
+
288
+ with row_dadosedicao_col2:
289
+ st.subheader('Países dos Artistas na Edição')
290
+ plotar_grafico(ch.get_grafico_pizza(info_edicao.get_lista_paises(), 'Quantidade', 'Pais', 'Músicas', 'País'))
291
+
292
+ with row_dadosedicao_col3:
293
+ st.subheader('Gêneros Musicais na Edição')
294
+ plotar_grafico(ch.get_grafico_pizza(info_edicao.get_lista_generos(), 'Quantidade', 'Genero', 'Músicas', 'Gênero Musical'))
295
+
296
+ st.divider()
297
+ if (ano_edicao != anos[0]):
298
+ row_edicaosubidas, row_edicaoquedas = st.columns((3.5, 3.5), gap="large")
299
+
300
+ with row_edicaosubidas:
301
+ st.subheader('Maiores subidas no ranking')
302
+ plotar_grafico(ch.get_grafico_slope(core.get_variacao_entre_anos(df_listagem, ano_edicao -1, ano_edicao, 5, False), 'Ano', ano_edicao - 1, ano_edicao, 'Posicao_Anterior', 'Posicao_Atual', 'Musica', 'Artista', 'Variaçãos no Ranking'))
303
+
304
+ with row_edicaoquedas:
305
+ st.subheader('Maiores quedas no ranking')
306
+ plotar_grafico(ch.get_grafico_slope(core.get_variacao_entre_anos(df_listagem, ano_edicao -1, ano_edicao, 5, True), 'Ano', ano_edicao - 1, ano_edicao, 'Posicao_Anterior', 'Posicao_Atual', 'Musica', 'Artista', 'Variaçãos no Ranking'))
307
+
308
+ st.divider()
309
+
310
+ st.subheader('Mapa de Gêneros Músicais')
311
+ plotar_grafico(ch.get_analise_edicao_treemap(info_edicao.get_lista_generos(), 'Genero', 'Quantidade', 'Gênero', 'Quantidade de Músicas'))
312
+
313
+ with tab_analises:
314
+ st.subheader('Análises por edição')
315
+ st.markdown('A análise de alguns aspectos por edição pode mostrar a diversidade de músicas, álbuns e gêneros musicais a cada edição.')
316
+ row_anelisemusica_col1, row_anelisemusica_col2 = st.columns((1.5, 6.2), gap="small")
317
+ with row_anelisemusica_col1:
318
+ analisemusica_edicao_selecionada = st.selectbox("Escolha o aspecto", list(list_analises_edicao.keys()), key = 'analise_edicao')
319
+ analisemusica_medida_selecionada = st.selectbox("Escolha a medida", medidas, key = 'medida_edicao')
320
+ with row_anelisemusica_col2:
321
+ plotar_grafico(ch.get_grafico_barra(core.get_analise_edicao(df_listagem_filtrada, analisemusica_medida_selecionada, list_analises_edicao[analisemusica_edicao_selecionada]),
322
+ "Edicao",
323
+ analisemusica_medida_selecionada,
324
+ "Edições",
325
+ analisemusica_medida_selecionada + ' de ' + analisemusica_edicao_selecionada))
326
+
327
+ st.divider()
328
+ st.subheader('One-Hit Wonders vs Recorrentes')
329
+ st.markdown('A análise de artistas que tiveram somente uma única música diferente em edições até hoje vs artistas que tiveram pelo menos duas músicas diferentes ajuda a compreender a preferência dos ouvintes')
330
+ plotar_grafico(ch.get_grafico_linha(core.get_onehit_por_edicao(df_listagem_filtrada), 'Edicao', 'Recorrentes', 'Edições', 'Artistas', 'Recorrentes', 'One_Hit_Wonders', 'One-Hit Wonders'))
331
+
332
+ st.divider()
333
+ st.subheader('Idade das músicas')
334
+ st.markdown('A análise de idade das músicas demonstra se há uma tradição de votação em músicas mais antigas (especialmente da década de 70) ou se têm sido incorporadas músicas mais recentes na listagem.')
335
+ st.markdown('A idade é recalculada a cada edição.')
336
+
337
+ plotar_grafico(ch.get_grafico_linha(core.get_idade_por_edicao(df_listagem_filtrada), 'Edicao', 'Media_Idade_Lancamento', 'Edições', 'Idade', 'Média de Idade', 'Mediana_Idade_Lancamento', 'Mediana de Idade'))
338
+
339
+ with tab_curiosidades:
340
+
341
+ info_curiosidades = InfoCuriosidade(core.filtrar_inconsistencias(df_listagem))
342
+
343
+ curiosidade = info_curiosidades.get_primeiro_artista_br()
344
+ st.markdown('* A primeira aparição de um artista brasileiro foi em {} com {}, ficando na {}ª posição.'.format(curiosidade[1], curiosidade[0], curiosidade[2]))
345
+
346
+ curiosidade = info_curiosidades.get_edicao_menos_artistas()
347
+ st.markdown('* A edição com menos artistas foi a {}, contando com "apenas" {} artistas.'.format(curiosidade[0], curiosidade[1]))
348
+
349
+ curiosidade = info_curiosidades.get_edicao_mais_artistas()
350
+ st.markdown('* Já a edição com mais artistas foi a {}, com {} artistas.'.format(curiosidade[0], curiosidade[1]))
351
+
352
+ curiosidade = info_curiosidades.get_artista_mais_musicas_edicao()
353
+ st.markdown('* O recorde de mais músicas em uma única edição é de {} com impressionantes {} músicas na edição {}.'.format(curiosidade[0], curiosidade[1], curiosidade[2]))
354
+
355
+ curiosidade = info_curiosidades.get_one_hit_wonder()
356
+ st.markdown('* {} ({}%) artistas aparceram nas edições com uma única música (os chamados "one-hit wonders")'.format(curiosidade[0], curiosidade[1]))
357
+
358
+ curiosidade = info_curiosidades.get_album_mais_musicas_edicao()
359
+ st.markdown('* O álbum/single com mais músicas em uma única edição é {} com {} músicas na edição {}.'.format(curiosidade[0], curiosidade[1], curiosidade[2]))
360
+
361
+ curiosidade = info_curiosidades.get_album_mais_musicas()
362
+ st.markdown('* O álbum/single com mais músicas em todas as edições é {} de {}, com {} músicas. Isto representa {} % de todas as músicas.'.format(curiosidade[1], curiosidade[0], curiosidade[2], curiosidade[3]))
363
+
364
+ curiosidade = info_curiosidades.get_duracao()
365
+ st.markdown('* A música com menor duração teve {} e a música com maior duração {}.'.format(curiosidade[0], curiosidade[1]))
366
+
367
+ curiosidade = info_curiosidades.get_artista_maior_percentual()
368
+ st.markdown('* {} é o artista com maior número de músicas: {}, o que representa {} % do total de músicas.'.format(curiosidade[0], curiosidade[1], curiosidade[2]))
369
+
370
+ with tab_predicoes:
371
+ row_predicoes_col1, row_predicoes_col2 = st.columns((3, 3.5), gap="small")
372
+
373
+ with row_predicoes_col1:
374
+ st.subheader('Predições das 500+ para {}'.format(max(anos)+1))
375
+ st.dataframe(core.get_predicoes(df_predicoes), hide_index=True, column_config={"posicao_ranking": st.column_config.Column("Posição", width=1), "Artista": "Artista", "Musica": "Música"})
376
+
377
+ with row_predicoes_col2:
378
+ st.subheader('Probabilidades da música aparecer em {}'.format(max(anos)+1))
379
+ st.dataframe(core.get_probabilidades(df_predicoes), hide_index=True, column_config={"Artista": "Artista", "Musica": "Música", "prob_aparecer": st.column_config.NumberColumn("Probabildiade de Aparecer", format="%.2f %%")})
380
+
381
+ with tab_edicoes:
382
+
383
+ rwo_ranking, row_videos= st.columns((3.8, 3.8), gap="small")
384
+
385
+ with rwo_ranking:
386
+ st.subheader('Top 10 de todas as edições')
387
+ components.top10(core.get_top_n_todas_edicoes(df_listagem, 10))
388
+ st.caption('Para entender como essa lista foi criada, consulte [a explicação](https://github.com/denisvirissimo/500mais-kissfm#as-maiores-de-todos-os-tempos).')
389
+ st.divider()
390
+
391
+ st.subheader('Mapa de calor de músicas presentes em todas as edições')
392
+ plotar_mapa_calor(ch.get_mapa_calor(core.get_musicas_todos_anos(df_listagem), "Edição", "Música", "Posição", "Edições", "Músicas"))
393
+
394
+ st.divider()
395
+
396
+ row_anelisemusica_col1, row_anelisemusica_col2= st.columns((3.5, 4.1), gap="small")
397
+ with row_anelisemusica_col1:
398
+ st.subheader('Informações da música')
399
+
400
+ lista_select_musicas = get_dicionario_musicas(df_listagem)
401
+ musica_selecionada = st.selectbox(
402
+ 'Escolha a música',
403
+ label_visibility='hidden',
404
+ options=lista_select_musicas.keys(),
405
+ index=None,
406
+ placeholder='Digite ou escolha a música',
407
+ format_func=lambda l: lista_select_musicas[l])
408
+
409
+ st.text('')
410
+
411
+ if (musica_selecionada != None):
412
+ row_infomusica_col1, row_infomusica_col2, row_infomusica_col3, row_infomusica_col4 = st.columns(4)
413
+ info_musica = InfoMusica(core.filtrar_inconsistencias(df_listagem), musica_selecionada)
414
+ row_infomusica_col1.metric(label="📈 Melhor Posição", value=str(info_musica.get_melhor_posicao()) + 'ª', delta=info_musica.get_edicao_melhor_posicao(), delta_color='off')
415
+ row_infomusica_col2.metric(label="📉 Pior Posição", value=str(info_musica.get_pior_posicao()) + "ª", delta=info_musica.get_edicao_pior_posicao(), delta_color='off')
416
+ row_infomusica_col3.metric(label="📊 Posição Média", value=str(info_musica.get_posicao_media()) + "ª")
417
+ row_infomusica_col4.metric(label="🗓️ Década", value=info_musica.get_decada())
418
+ st.text('')
419
+ row_infomusica_col5, row_infomusica_col6, row_infomusica_col7, row_infomusica_col8= st.columns(4)
420
+ row_infomusica_col5.metric(label="#️⃣ Número Aparições", value=info_musica.get_numero_aparicoes())
421
+ row_infomusica_col6.metric(label='🔥 Aparições Consecutivas', value=info_musica.get_numero_aparicoes_consecutivas())
422
+ row_infomusica_col7.metric(label='🏅 Número Pódios', value=info_musica.get_numero_podios())
423
+ row_infomusica_col8.metric(label='🏅 Pódios Consecutivos', value=info_musica.get_numero_podios_consecutivos())
424
+
425
+ st.subheader('Histórico')
426
+ plotar_grafico(ch.get_grafico_linha(info_musica.get_posicoes(),'Ano', 'Posicao', 'Ano', 'Posição no ranking', '', reversed=True))
427
+
428
+ st.divider()
429
+
430
+ row_aneliseartista_col1, row_aneliseartista_col2= st.columns((3.5, 4.1), gap="small")
431
+ with row_aneliseartista_col1:
432
+ st.subheader('Informações do artista')
433
+
434
+ lista_select_artistas = get_dicionario_artistas(df_listagem)
435
+ artista_selecionado = st.selectbox(
436
+ 'Escolha o artista',
437
+ label_visibility='hidden',
438
+ options=lista_select_artistas.keys(),
439
+ index=None,
440
+ placeholder='Digite ou escolha o artista',
441
+ format_func=lambda l: lista_select_artistas[l])
442
+
443
+ st.text('')
444
+
445
+ if (artista_selecionado != None):
446
+ row_infoartista_col1, row_infoartista_col2, row_infoartista_col3, row_infoartista_col4 = st.columns(4)
447
+ info_artista = InfoArtista(core.filtrar_inconsistencias(df_listagem), artista_selecionado)
448
+ row_infoartista_col1.metric(label="📈 Melhor Posição", value=str(info_artista.get_melhor_posicao()) + 'ª', delta=info_artista.get_edicao_melhor_posicao(), delta_color='off')
449
+ row_infoartista_col2.metric(label="📉 Pior Posição", value=str(info_artista.get_pior_posicao()) + "ª", delta=info_artista.get_edicao_pior_posicao(), delta_color='off')
450
+ row_infoartista_col3.metric(label="🎶 Total Músicas", value=info_artista.get_total_musicas())
451
+ row_infoartista_col4.metric(label="️#️⃣ Número Edições", value=info_artista.get_total_edicoes())
452
+ st.text('')
453
+ row_infoartista_col5, row_infoartista_col6, row_infoartista_col7, row_infoartista_col8= st.columns(4)
454
+ row_infoartista_col5.metric(label="️🎵Média Músicas", value=locale.format_string("%.2f", info_artista.get_media_musicas_por_edicao(), grouping = True), delta="por edição", delta_color='off')
455
+ row_infoartista_col6.metric(label='🔥 Aparições Consecutivas', value=info_artista.get_numero_aparicoes_consecutivas())
456
+ row_infoartista_col7.metric(label='🏅 Número Pódios', value=info_artista.get_numero_podios())
457
+ row_infoartista_col8.metric(label='🏅 Pódios Consecutivos', value=info_artista.get_numero_podios_consecutivos())
458
+
459
+ with row_videos:
460
+
461
+ st.subheader('')
462
+ plotar_grafico_race(core.get_dados_cumulativos(load_data(False), 'Artista'),
463
+ 'Artista',
464
+ 'Top 10 Artistas com mais músicas nas edições')
465
+
466
+ plotar_grafico_race(core. get_dados_cumulativos(load_data(False), 'Genero'),
467
+ 'Genero',
468
+ 'Top 10 Gêneros Musicais com mais músicas nas edições')
charts.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import plotly.express as px
2
+ import plotly.graph_objects as go
3
+ import bar_chart_race as bcr
4
+
5
+ def get_grafico_linha(df_data, xdata, ydata1, xlabel, ylabel, ylabel1, ydata2 = None, ylabel2 = None, reversed = False):
6
+ fig = px.line()
7
+ fig.update_layout(xaxis_type='category', xaxis_title = xlabel, yaxis_title=ylabel, separators=',.')
8
+ fig.add_scatter(x=df_data[xdata], y=df_data[ydata1], name=ylabel1)
9
+ if (ydata2 != None):
10
+ fig.add_scatter(x=df_data[xdata], y=df_data[ydata2], name=ylabel2)
11
+ fig.update_traces(hovertemplate=xlabel + ': %{x}<br> Valor: %{y}<extra></extra>')
12
+ if (reversed):
13
+ fig.update_layout(yaxis=dict(autorange='reversed'))
14
+
15
+ return fig
16
+
17
+ def get_grafico_barra(df_data, xdata, ydata, xlabel, ylabel, x_diagonal=False):
18
+ fig = px.bar(df_data, x=xdata, y=ydata, text_auto=True)
19
+ fig.update_layout(xaxis_type='category', xaxis_title = xlabel, yaxis_title=ylabel, separators=',.')
20
+ fig.update_traces(marker_color='#C50B11', hovertemplate=xlabel + ": %{x}<br>" + ylabel + ": %{y}", textangle=0)
21
+ if x_diagonal:
22
+ fig.update_xaxes(tickangle=-45)
23
+ if (df_data.select_dtypes(include='datetime').columns.size > 0):
24
+ fig.update_layout(yaxis_tickformat="%M:%S")
25
+
26
+ return fig
27
+
28
+ def get_grafico_barra_horizontal(df_data, xdata, ydata, xlabel, ylabel, x_diagonal=False):
29
+ df = df_data.sort_values(xdata, ascending = True)
30
+
31
+ fig = go.Figure(go.Bar(
32
+ x = df[xdata],
33
+ y = df[ydata],
34
+ hoverinfo = 'all',
35
+ name='',
36
+ textposition = 'outside',
37
+ texttemplate='%{x}',
38
+ hovertemplate = xlabel + ": %{x}<br>" + ylabel + ": %{y}",
39
+ orientation = 'h',
40
+ marker=dict(color='#C50B11'))
41
+ )
42
+
43
+ return fig
44
+
45
+ def get_grafico_barra_stacked(df_data, xdata, ydata, ldata, xlabel, ylabel, llabel):
46
+ fig = px.bar(df_data, x=xdata, y=ydata, color=ldata, color_discrete_sequence=px.colors.qualitative.Dark24, barmode='stack')
47
+ fig.update_layout(xaxis_type='category', xaxis_title = xlabel, yaxis_title=ylabel, legend_title=llabel, legend_traceorder="reversed")
48
+ fig.update_traces(hovertemplate='%{fullData.name}<br>' + xlabel + ": %{label}<br>" + ylabel + ": %{value}<extra></extra>")
49
+ fig.update_xaxes(categoryorder='array', categoryarray=df_data.sort_values(xdata)[xdata].to_list())
50
+
51
+ return fig
52
+
53
+ def get_grafico_pizza(df_data, valor, nomes, label_valor, label_nomes):
54
+ fig = px.pie(df_data, values=valor, names=nomes)
55
+ fig.update_traces(textposition='inside', textinfo='percent+label', hovertemplate=label_nomes + ": %{label}<br>" + label_valor + ": %{value}<br>" + 'Percentual' + ": %{percent}<br>")
56
+ fig.update_layout(
57
+ separators=',.',
58
+ uniformtext_minsize=12, uniformtext_mode='hide',
59
+ legend=dict(font=dict(size=14)),
60
+ margin=dict(
61
+ l=0,
62
+ r=0,
63
+ b=20,
64
+ t=50,
65
+ pad=0
66
+ ))
67
+
68
+ return fig
69
+
70
+ def get_mapa(df_data, locations, color, hover_name, title):
71
+ fig = px.choropleth(df_data,
72
+ locationmode="country names",
73
+ locations=locations,
74
+ color=color,
75
+ hover_name=hover_name,
76
+ color_continuous_scale = px.colors.sequential.YlOrRd, projection='natural earth')
77
+
78
+ fig.update_layout(coloraxis_colorbar=dict(title=title))
79
+
80
+ return fig
81
+
82
+ def get_mapa_calor(df_data, xhover, yhover, zhover, xlabel, ylabel):
83
+ fig = go.Figure(data=go.Heatmap(
84
+ z=df_data,
85
+ x=df_data.columns,
86
+ y=df_data.index,
87
+ text=df_data,
88
+ colorscale='viridis',
89
+ reversescale=True,
90
+ name="",
91
+ hovertemplate= xhover + ': %{x}<br>' + yhover + ': %{y}<br>' + zhover + ': %{z}',
92
+ texttemplate="%{text}"))
93
+
94
+ fig.update_layout(xaxis_type='category',
95
+ xaxis_title = xlabel,
96
+ yaxis_title = ylabel,
97
+ height=55*len(df_data.index),
98
+ dragmode=False,
99
+ clickmode='none',
100
+ showlegend=False)
101
+
102
+ fig.update_yaxes(tickvals=df_data.index, ticktext=[label + ' ' for label in df_data.index])
103
+ fig['layout']['yaxis']['autorange'] = "reversed"
104
+
105
+ return fig
106
+
107
+ def get_analise_edicao_treemap(df_data, xdata, ydata, xlabel, ylabel):
108
+ fig = px.treemap(df_data, path=[px.Constant('Todos'), xdata], values=ydata, color=xdata, hover_data=[xdata])
109
+ fig.update_layout(margin = dict(t=50, l=25, r=25, b=25))
110
+ fig.update_traces(hovertemplate=xlabel + ": %{label}<br>" + ylabel + ": %{value}")
111
+
112
+ return fig
113
+
114
+ def gerar_grafico_race(df_data, atributo, titulo):
115
+ df_values, df_ranks = bcr.prepare_long_data(df_data, index='Ano', columns=atributo, values='Count', steps_per_period=1)
116
+ return bcr.bar_chart_race(df_values,
117
+ n_bars=10,
118
+ steps_per_period=15,
119
+ period_length=1000,
120
+ title = titulo,
121
+ period_template='{x:.0f}',
122
+ bar_texttemplate='{x:.0f}',
123
+ tick_template='{x:.0f}',
124
+ fixed_max=True,
125
+ filter_column_colors=True).data
126
+
127
+ def get_grafico_slope(df_data, xlabel, xdata1, xdata2, ydata1, ydata2, legend1, legend2, title):
128
+ fig = go.Figure()
129
+
130
+ for _, row in df_data.iterrows():
131
+ fig.add_trace(go.Scatter(
132
+ y=[row[ydata1], row[ydata2]],
133
+ mode='lines+markers+text',
134
+ name=f"{row[legend1]} - {row[legend2]}",
135
+ text=[int(row[xdata1]), int(row[xdata2])],
136
+ textposition='bottom right',
137
+ line=dict(width=2),
138
+ hoverinfo='none',
139
+ ))
140
+
141
+ fig.update_layout(yaxis=dict(autorange='reversed', title=title, showticklabels=False),
142
+ xaxis=dict(
143
+ tickvals=[0, 1],
144
+ ticktext=[xdata1,xdata2],
145
+ title=xlabel
146
+ ),
147
+ height=600,
148
+ legend=dict(
149
+ orientation="h",
150
+ yanchor="bottom",
151
+ y=-0.3,
152
+ xanchor="center",
153
+ x=0.5
154
+ ))
155
+ return fig
components.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit.components.v1 as components
3
+
4
+ css_file = './resources/style.css'
5
+
6
+ @st.cache_data
7
+ def load_css():
8
+ with open(css_file) as f:
9
+ return f'<style>{f.read()}</style>'
10
+
11
+ def top10(df_data):
12
+ html = load_css()
13
+ html+="""
14
+
15
+ <div class="list">
16
+ <div class="list__body">
17
+ <table class="list__table">
18
+ <tbody>
19
+ """
20
+
21
+ for index, row in df_data.iterrows():
22
+ html += '<tr class="list__row"><td class="list__cell"><span class="list__value">' + str(row.Posicao_Atual) +'</span></td>'
23
+ html += '<td class="list__cell"><span class="list__value">'+row.Musica+'</span><small class="list__label"></small></td>'
24
+ html += '<td class="list__cell"><span class="list__value">'+row.Artista+'</span><small class="list__label"></small>'
25
+ if (row.Variacao > 0):
26
+ html += '</td><td class="list__cell list__icon__green">▲ ' + str(row.Variacao) + '</td></tr>'
27
+ elif (row.Variacao < 0):
28
+ html += '</td><td class="list__cell list__icon__red">▼ ' + str(row.Variacao * -1) + '</td></tr>'
29
+ else:
30
+ html += '</td><td class="list__cell list__icon__grey">■ 0</td></tr>'
31
+
32
+ html+="""
33
+ </tbody></table>
34
+ </div>
35
+ </div>
36
+
37
+ """
38
+ return components.html(html, height=600, width=650)
core_functions.py ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import io
4
+ import time
5
+
6
+ #Configuração
7
+ pd.set_option("styler.render.max_elements", 350000)
8
+ dataset_file = './data/500+.csv'
9
+ predictions_file = './data/predicao_proximo_ano.csv'
10
+
11
+ #Inicialização
12
+ def load_data(agregar_pinkfloyd):
13
+ df_data = pd.read_csv(dataset_file)
14
+ df_data['Id'] = range(1, len(df_data) + 1)
15
+ df_data['Edicao'] = df_data.Ano.astype(str).str[-2:] + "-" + (df_data.Ano +1).astype(str).str[-2:]
16
+ df_data['Data_Lancamento_Album'] = pd.to_datetime(df_data['Data_Lancamento_Album'])
17
+ df_data['Decada_Lancamento_Album'] = df_data['Data_Lancamento_Album'].dt.year.apply(get_decada)
18
+ df_data['Duracao'] = df_data.loc[:,'Duracao'].fillna(value=0)
19
+ df_data['Duracao_Formatada'] = df_data.apply(lambda row: time.strftime("%M:%S", time.gmtime(row['Duracao'])), axis=1)
20
+ if (agregar_pinkfloyd):
21
+ df_data.loc[df_data['Musica'].str.contains('Another Brick', na=False), 'Musica'] = 'Another Brick in the Wall'
22
+ df_data.loc[df_data['Musica'].str.contains('Another Brick', na=False), 'Duracao'] = 508
23
+
24
+ return df_data
25
+
26
+ def load_predicoes():
27
+ df_data = pd.read_csv(predictions_file)
28
+ return df_data
29
+
30
+ #Funções
31
+ def get_decada(ano):
32
+ return 'Anos ' + str(ano)[2] + '0'
33
+
34
+ def listar_edicoes(df_data):
35
+ return np.array(np.unique(df_data.Edicao).tolist())
36
+
37
+ def listar_posicoes(df_data):
38
+ return np.unique(df_data.Posicao).tolist()
39
+
40
+ def listar_anos_lancamento(df_data):
41
+ return np.unique(df_data.Data_Lancamento_Album.dropna().dt.year.apply(lambda x: f'{x:.0f}')).tolist()
42
+
43
+ def listar_anos_edicoes(df_data):
44
+ return np.array(np.unique(df_data.Ano).tolist())
45
+
46
+ def filtrar_edicao(df_data, edicao_inicial, edicao_final):
47
+ edicoes = np.unique(df_data.Edicao).tolist()
48
+ indice_inicial = edicoes.index(edicao_inicial)
49
+ indice_final = edicoes.index(edicao_final)+1
50
+ edicoes_selecionadas = edicoes[indice_inicial:indice_final]
51
+ return df_data[df_data['Edicao'].isin(edicoes_selecionadas)]
52
+
53
+ def filtrar_posicoes(df_data, posicao_inicial, posicao_final):
54
+ posicoes = list(range(posicao_inicial, posicao_final + 1))
55
+ return df_data[df_data['Posicao'].isin(posicoes)]
56
+
57
+ def filtrar_anos(df_data, ano_inicial, ano_final):
58
+ anos = list(range(int(ano_inicial), int(ano_final) + 1))
59
+ return df_data[df_data['Data_Lancamento_Album'].dt.year.isin(anos)]
60
+
61
+ def filtrar_inconsistencias(df_data):
62
+ return df_data.loc[(df_data['Artista'] != '???') & (df_data['Musica'].str.len() > 0) & (df_data['Observacao'] != 'repetida')]
63
+
64
+ def get_primeiro_ano(df_data):
65
+ return df_data.sort_values(by='Ano').head(1)['Ano']
66
+
67
+ def get_ultimo_ano(df_data):
68
+ return df_data.sort_values(by='Ano').tail(1)['Ano']
69
+
70
+ def get_primeira_edicao(df_data):
71
+ return df_data.sort_values(by='Ano').head(1)['Edicao']
72
+
73
+ def get_ultima_edicao(df_data):
74
+ return df_data.sort_values(by='Ano').tail(1)['Edicao']
75
+
76
+ def get_primeiro_ano_lancamento(df_data):
77
+ return df_data.dropna(subset=['Musica']).sort_values(by = 'Data_Lancamento_Album').head(1)['Data_Lancamento_Album'].dt.year
78
+
79
+ def get_ultimo_ano_lancamento(df_data):
80
+ return df_data.dropna(subset=['Musica']).sort_values(by = 'Data_Lancamento_Album').tail(1)['Data_Lancamento_Album'].dt.year
81
+
82
+ def get_total_musicas_distintas(df_data):
83
+ return len(get_musicas_distintas(df_data))
84
+
85
+ def get_total_artistas_distintos(df_data):
86
+ return len(np.unique(df_data.Artista.dropna()).tolist())
87
+
88
+ def get_total_albuns_distintos(df_data):
89
+ return len(np.unique(df_data.Album_Single.dropna().astype(str)).tolist())
90
+
91
+ def get_total_paises_distintos(df_data):
92
+ return len(np.unique(df_data.Pais.dropna()).tolist())
93
+
94
+ def get_total_generos_distintos(df_data):
95
+ return len(get_generos_distintos(df_data))
96
+
97
+ def get_musicas_distintas(df_data):
98
+ return filtrar_inconsistencias(df_data).drop_duplicates(subset=['Artista', 'Musica', 'Observacao'])
99
+
100
+ def get_generos_distintos(df_data):
101
+ return filtrar_inconsistencias(df_data).drop_duplicates(subset='Genero')
102
+
103
+ def get_total_horas(df_data):
104
+ return np.sum(df_data.Duracao.dropna()) / 3600
105
+
106
+ def get_dicionario_musicas(df_data):
107
+ df = (filtrar_inconsistencias(df_data)
108
+ .drop_duplicates(subset={'Artista', 'Musica'})
109
+ .apply(lambda row: (row['Musica'] + ' (' + row['Artista'] + ')', row['Id']), axis=1)
110
+ .sort_values()
111
+ .tolist())
112
+
113
+ return dict((y, x) for x, y in df)
114
+
115
+ def get_dicionario_artistas(df_data):
116
+ df = (filtrar_inconsistencias(df_data)
117
+ .drop_duplicates('Artista')
118
+ .apply(lambda row: (row['Artista'], row['Artista']), axis=1)
119
+ .sort_values()
120
+ .tolist())
121
+
122
+ return dict((y, x) for x, y in df)
123
+
124
+ def get_acumulado_musicas_distintas(df_data):
125
+ edicoes = np.unique(df_data.Edicao).tolist()
126
+ distinta_acumulado_periodo = []
127
+ for e in edicoes:
128
+ distinta_acumulado_periodo.append(get_total_musicas_distintas(filtrar_edicao(df_data, edicoes[0], e)))
129
+ return pd.DataFrame({'Anos': edicoes, 'Acumulado': distinta_acumulado_periodo})
130
+
131
+ def get_acumulado_generos_distintos(df_data):
132
+ edicoes = np.unique(df_data.Edicao).tolist()
133
+ distinto_acumulado_periodo = []
134
+ for e in edicoes:
135
+ distinto_acumulado_periodo.append(get_total_generos_distintos(filtrar_edicao(df_data, edicoes[0], e)))
136
+ return pd.DataFrame({'Anos': edicoes, 'Acumulado': distinto_acumulado_periodo})
137
+
138
+ def get_musicas_ano_lancamento(df_data):
139
+ df_temp = get_musicas_distintas(df_data)
140
+ return pd.DataFrame(df_temp.groupby(df_temp['Data_Lancamento_Album'].dt.year).size().reset_index().rename(columns={0: 'Total_Musicas'}))
141
+
142
+ def get_musicas_decada_lancamento(df_data):
143
+ df_temp = get_musicas_distintas(df_data)
144
+ df_temp['Total_Musicas'] = df_temp.groupby('Decada_Lancamento_Album')['Decada_Lancamento_Album'].transform('count')
145
+ return pd.DataFrame(df_temp.sort_values('Data_Lancamento_Album').groupby(['Decada_Lancamento_Album', 'Total_Musicas']).head(1))[['Decada_Lancamento_Album', 'Total_Musicas']]
146
+
147
+ def get_musicas_todos_anos(df_data):
148
+ df = filtrar_inconsistencias(df_data).copy()
149
+ df['Count'] = df.groupby(['Artista', 'Musica', 'Observacao'], dropna=False)['Musica'].transform('count')
150
+ df['Musica'] = df.apply(lambda row: row['Artista'] + ' - ' + row['Musica'], axis=1)
151
+ df = df.loc[df['Count'] == df['Ano'].nunique()].sort_values(['Ano','Posicao'])
152
+
153
+ return pd.pivot(data=df, index='Musica', columns='Edicao', values='Posicao')
154
+
155
+ def get_musicas_por_pais(df_data, agrupar_edicoes=False):
156
+ df = filtrar_inconsistencias(df_data)
157
+ if (agrupar_edicoes):
158
+ return df.groupby(['Country', 'Pais']).size().reset_index(name='Total_Musicas')
159
+ else:
160
+ return (df.groupby(['Edicao', 'Pais'])
161
+ .size()
162
+ .reset_index(name='Total_Musicas')
163
+ .groupby(['Edicao', 'Pais'])
164
+ .agg({'Total_Musicas': 'sum'})
165
+ .reset_index()
166
+ .sort_values(by='Edicao')
167
+ .sort_values(by='Total_Musicas', ascending=True))
168
+
169
+ def get_musicas_por_genero(df_data):
170
+ df = filtrar_inconsistencias(df_data)
171
+ return (df.groupby(['Edicao', 'Genero'])
172
+ .size()
173
+ .reset_index(name='Total_Musicas')
174
+ .groupby(['Edicao', 'Genero'])
175
+ .agg({'Total_Musicas': 'sum'})
176
+ .reset_index()
177
+ .sort_values(by='Edicao')
178
+ .sort_values(by='Total_Musicas', ascending=True))
179
+
180
+ def get_musicas_media_posicao(df_data):
181
+ #Fórmula Si = wi * Ai + (1 - wi) * S, em que:
182
+ #wi = mi/mi+m_avg, sendo mi número total de aparições da música e m_avg média de todas as aparições de músicas
183
+ #Ai = média aritmética da posição da música
184
+ #S = média aritmética da posição de todas as músicas
185
+ #Si = média bayesiana da posição da música
186
+ #https://arpitbhayani.me/blogs/bayesian-average/
187
+
188
+ df_distintas = filtrar_inconsistencias(df_data.copy())
189
+
190
+ #Workaround devido a problema de index com NaN no pivot_table. Necessário preencher o que está NaN com um valor dummy para poder fazer o grouping
191
+ #https://github.com/pandas-dev/pandas/issues/3729
192
+ df_distintas['Observacao'] = df_distintas['Observacao'].fillna('dummy')
193
+
194
+ df_totalizador = (df_distintas
195
+ .groupby(['Artista', 'Musica', 'Observacao'], dropna=False)
196
+ .size()
197
+ .reset_index(name='Total_Aparicoes'))
198
+
199
+ m_avg = df_totalizador['Total_Aparicoes'].mean()
200
+
201
+ pivot_table = (pd.pivot_table(df_distintas,
202
+ index=['Artista', 'Musica', 'Observacao'],
203
+ columns='Ano',
204
+ values='Posicao',
205
+ margins=True,
206
+ margins_name = 'Media_Posicao'))
207
+
208
+ S = pivot_table.loc[('Media_Posicao', '', ''), 'Media_Posicao']
209
+
210
+ newdf = (df_distintas
211
+ .groupby(['Artista', 'Musica', 'Observacao'], dropna=False)
212
+ .size()
213
+ .reset_index(name='Total_Aparicoes'))
214
+
215
+ merged_df = pd.merge(df_totalizador, pivot_table, on = ['Artista', 'Musica', 'Observacao'])
216
+
217
+ merged_df['Media_Bayesiana_Posicao'] = get_bayesian_average(merged_df['Total_Aparicoes'], m_avg, merged_df['Media_Posicao'], S)
218
+
219
+ return merged_df.sort_values('Media_Bayesiana_Posicao')
220
+
221
+ def get_bayesian_average(m, m_avg, A, S):
222
+ w = m/(m+m_avg)
223
+ return w * A + (1-w) * S
224
+
225
+ def get_artistas_top_n(df_data, top_n):
226
+ df = filtrar_posicoes(df_data, 1, top_n)
227
+ df = (filtrar_inconsistencias(df)
228
+ .groupby('Artista')
229
+ .size()
230
+ .sort_values(ascending=False)
231
+ .reset_index(name='Total_Aparicoes'))
232
+ return df
233
+
234
+ def get_musicas_top_n(df_data, top_n):
235
+ df = filtrar_posicoes(df_data, 1, top_n)
236
+ df = (filtrar_inconsistencias(df)
237
+ .groupby(['Artista', 'Musica'])
238
+ .size()
239
+ .sort_values(ascending=False)
240
+ .reset_index(name='Total_Aparicoes'))
241
+ return df
242
+
243
+ def get_albuns_top_n(df_data, top_n):
244
+ df = filtrar_posicoes(df_data, 1, top_n)
245
+ df = (filtrar_inconsistencias(df)
246
+ .groupby(['Artista', 'Album_Single'])
247
+ .size()
248
+ .sort_values(ascending=False)
249
+ .reset_index(name='Total_Aparicoes'))
250
+ return df
251
+
252
+ def get_generos_top_n(df_data, top_n):
253
+ df = filtrar_posicoes(df_data, 1, top_n)
254
+ df = (filtrar_inconsistencias(df)
255
+ .groupby('Genero')
256
+ .size()
257
+ .sort_values(ascending=False)
258
+ .reset_index(name='Total_Aparicoes'))
259
+ return df
260
+
261
+ def get_artistas_posicoes_semelhantes_top_n(df_data, top_n):
262
+ def analisar_retorno(grupo):
263
+ grupo = grupo.sort_values('Ano')
264
+ grupo['Posicao_Anterior'] = grupo['Posicao'].shift(1)
265
+ return grupo
266
+
267
+ df = df_data.groupby('Musica', group_keys=False)[['Ano', 'Posicao', 'Artista', 'Musica']].apply(analisar_retorno).dropna(subset=['Posicao_Anterior'])
268
+ df['Posicao_Semelhante'] = np.abs(df['Posicao'] - df['Posicao_Anterior']) <= 5
269
+
270
+ df = (df.groupby('Artista')['Posicao_Semelhante']
271
+ .mean()
272
+ .reset_index())
273
+
274
+ return df.sort_values(by=['Posicao_Semelhante', 'Artista'], ascending=[False, True]).head(top_n)
275
+
276
+ def get_top_n_musicas_media_posicao(df_data, top_n):
277
+ df = get_musicas_media_posicao(df_data).loc[:,['Artista', 'Musica']]
278
+ df['Posicao'] = range(1, len(df) + 1)
279
+ return df[['Posicao', 'Artista', 'Musica']].head(top_n).set_index('Posicao')
280
+
281
+ def get_top_n_todas_edicoes(df_data, top_n):
282
+ edicoes = np.unique(df_data.Edicao)
283
+ edicao_inicial = edicoes[0]
284
+ edicao_anterior = edicoes[len(edicoes) -2]
285
+ df1 = get_top_n_musicas_media_posicao(df_data, top_n).reset_index()
286
+ df2 = get_top_n_musicas_media_posicao(filtrar_edicao(df_data, edicao_inicial, edicao_anterior), 100).reset_index()
287
+
288
+ merged_df = pd.merge(df1, df2, how='left', on = ['Artista', 'Musica'], suffixes=('_Atual', '_Anterior'))
289
+ merged_df['Variacao'] = merged_df['Posicao_Anterior'] - merged_df['Posicao_Atual']
290
+
291
+ return merged_df
292
+
293
+ def get_melhor_posicao_genero(df_data):
294
+ df = df = df_data.sort_values('Ano')
295
+ indexes = df.groupby(['Genero'])['Posicao'].idxmin()
296
+ return df.loc[indexes, ['Genero', 'Posicao', 'Edicao']]
297
+
298
+ def get_analise_edicao(df_data, medida, analise):
299
+ agregadores = {"Musica_Artista":['Artista', 'Edicao'],
300
+ "Album_Artista":['Album_Single', 'Edicao'],
301
+ "Musica_Genero":['Genero', 'Edicao'],
302
+ "Genero_Pais":['Pais','Edicao'],
303
+ "Duracao":['Duracao','Edicao']}
304
+
305
+ dimensoes = {"Musica_Artista":'Musica',
306
+ "Album_Artista":'Musica',
307
+ "Musica_Genero":'Musica',
308
+ "Genero_Pais":'Genero',
309
+ "Duracao":'Duracao'}
310
+
311
+ agregador = agregadores[analise]
312
+ dimensao = dimensoes[analise]
313
+ index_name = 'Contagem'
314
+
315
+ df = filtrar_inconsistencias(df_data)
316
+ if (dimensao != 'Duracao'):
317
+ df = df.groupby(agregador)[dimensao].count().reset_index(name=index_name)
318
+ else:
319
+ index_name = dimensao
320
+
321
+ match medida:
322
+ case 'Média':
323
+ df = df.groupby('Edicao')[index_name].mean().reset_index(name=medida)
324
+ case 'Mediana':
325
+ df = df.groupby('Edicao')[index_name].median().reset_index(name=medida)
326
+ case 'Máximo':
327
+ df = df.groupby('Edicao')[index_name].max().reset_index(name=medida)
328
+ case default:
329
+ df = df
330
+
331
+ if (dimensao == 'Duracao'):
332
+ df[medida] = pd.to_datetime(df[medida], unit='s')
333
+
334
+ return np.around(df,2)
335
+
336
+ def get_idade_por_edicao(df_data):
337
+ df = df_data.copy()
338
+ df = filtrar_inconsistencias(df)
339
+ df['Idade_Lancamento'] = df['Ano'] + 1 - df['Data_Lancamento_Album'].dt.year
340
+
341
+ df = df.loc[:,['Edicao', 'Idade_Lancamento']]
342
+ df['Media_Idade_Lancamento'] = df.groupby('Edicao')['Idade_Lancamento'].transform('mean').round(2)
343
+ df['Mediana_Idade_Lancamento'] = df.groupby('Edicao')['Idade_Lancamento'].transform('median').round(0)
344
+
345
+ return df.groupby(['Edicao', 'Media_Idade_Lancamento', 'Mediana_Idade_Lancamento']).size().reset_index()
346
+
347
+ def get_onehit_por_edicao(df_data):
348
+ df = df_data.copy()
349
+ df = filtrar_inconsistencias(df)
350
+
351
+ contagem = get_musicas_distintas(df).groupby('Artista').count().reset_index()[['Artista', 'Ano']]
352
+ contagem.columns = ['Artista', 'Count']
353
+
354
+ one_hit_wonders = contagem[contagem['Count'] == 1].sort_values(by='Artista')
355
+ one_hit_wonders = (pd.merge(df, one_hit_wonders[['Artista']], on='Artista', how='inner')
356
+ .groupby('Edicao')['Artista']
357
+ .nunique()
358
+ .reset_index(name='One_Hit_Wonders'))
359
+
360
+ artistas_recorrentes = contagem[contagem['Count'] > 1].sort_values(by='Count', ascending=False)
361
+ artistas_recorrentes = (pd.merge(df, artistas_recorrentes[['Artista']], on='Artista', how='inner')
362
+ .groupby('Edicao')['Artista']
363
+ .nunique()
364
+ .reset_index(name='Recorrentes'))
365
+
366
+ return pd.merge(one_hit_wonders, artistas_recorrentes, on='Edicao', how='outer').fillna(0)
367
+
368
+
369
+ def get_dados_cumulativos(df_data, atributo):
370
+ df_data = filtrar_inconsistencias(df_data)
371
+ df_data = (df_data.groupby(['Ano', atributo])
372
+ .size()
373
+ .reset_index(name='Count')
374
+ .groupby(['Ano', atributo])['Count']
375
+ .sum()
376
+ .groupby(level=atributo)
377
+ .cumsum()
378
+ .reset_index())
379
+ df_data = df_data.sort_values(by='Count', ascending=False).groupby('Ano').head(len(df_data))
380
+
381
+ return df_data
382
+
383
+ def get_variacao_entre_anos(df, ano_inicial, ano_final, quantidade_musicas, quedas):
384
+ anos_para_comparar = [ano_inicial, ano_final]
385
+
386
+ df_sorted = df.sort_values(by=['Musica', 'Artista', 'Ano'])
387
+ df_sorted = df_sorted[df_sorted['Ano'].isin(anos_para_comparar)]
388
+
389
+ pivot = df_sorted.pivot_table(index=['Musica', 'Artista'], columns='Ano', values='Posicao').reset_index()
390
+
391
+ pivot.columns.name = None
392
+ pivot = pivot.rename(columns={
393
+ anos_para_comparar[0]: ano_inicial,
394
+ anos_para_comparar[1]: ano_final
395
+ })
396
+
397
+ pivot['Variacao'] = pivot[ano_inicial] - pivot[ano_final]
398
+
399
+ if (quedas):
400
+ top_n = pivot.sort_values(by='Variacao').head(quantidade_musicas)
401
+ top_n['Posicao_Anterior'] = top_n[ano_inicial] *1.5
402
+ top_n['Posicao_Atual'] = top_n[ano_final]
403
+ else:
404
+ top_n = pivot.sort_values(by='Variacao', ascending=False).head(quantidade_musicas)
405
+ top_n['Posicao_Anterior'] = top_n[ano_inicial]
406
+ top_n['Posicao_Atual'] = top_n[ano_final] *1.5
407
+
408
+ return top_n
409
+
410
+ def get_predicoes(df):
411
+ df = df[["posicao_ranking", "Artista", "Musica"]].head(500)
412
+ return df
413
+
414
+ def get_probabilidades(df):
415
+ df = df.sort_values(by=['prob_aparecer', 'Artista', 'Musica'], ascending=[False, True, True])
416
+ df["prob_aparecer"] = df["prob_aparecer"] * 100
417
+ df = df[["Artista", "Musica", "prob_aparecer"]]
418
+ return df
data/500+.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/500+_openrefine.tar.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89e36f8063852d7d46e092b67eee32cfcc96962e08366e3143a26d5d47675f46
3
+ size 2554300
data/predicao_proximo_ano.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/raw/500+_raw.xlsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:18c72765e769e01f1bbd231470109d622f539e9997a5b3257965c18634e03df7
3
+ size 666557
info.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import pandas as pd
3
+ import numpy as np
4
+
5
+ class InfoBase:
6
+
7
+ def _listar_podios(self, df):
8
+ return df[df['Posicao'].isin({1,2,3})]
9
+
10
+ def _contar_consecutivos(self, df):
11
+ df = df.reset_index()
12
+ diff = df['Ano'] + df.index
13
+ return df.groupby(diff)['Ano'].size().max()
14
+
15
+ def get_melhor_posicao(self):
16
+ return np.min(self.df.Posicao)
17
+
18
+ def get_pior_posicao(self):
19
+ return np.max(self.df.Posicao)
20
+
21
+ def get_numero_aparicoes_consecutivas(self):
22
+ return self._contar_consecutivos(self.df)
23
+
24
+ def get_numero_podios_consecutivos(self):
25
+ return np.nan_to_num(self._contar_consecutivos(self._listar_podios(self.df))).astype('int')
26
+
27
+ def get_edicao_melhor_posicao(self):
28
+ return "Edição " + self.df[self.df['Posicao'] == self.get_melhor_posicao()].Edicao.values[-1]
29
+
30
+ def get_edicao_pior_posicao(self):
31
+ return "Edição " + self.df[self.df['Posicao'] == self.get_pior_posicao()].Edicao.values[-1]
32
+
33
+ class InfoEdicao:
34
+
35
+ def __init__(self, df_data, ano):
36
+ self.df = df_data[df_data['Ano'] == ano]
37
+
38
+ def get_musica_posicao(self, posicao):
39
+ df_filtrado = self.df[self.df['Posicao'] == posicao]
40
+ return df_filtrado.Artista.values[0] + ' - ' + df_filtrado.Musica.values[0]
41
+
42
+ def get_musica_menor_duracao(self):
43
+ df_filtrado = self.df.dropna(subset='Musica').sort_values(by='Duracao').head(1)
44
+ return df_filtrado.Artista.values[0] + ' - ' + df_filtrado.Musica.values[0] + ' (' + df_filtrado.Duracao_Formatada.values[0] + ')'
45
+
46
+ def get_musica_maior_duracao(self):
47
+ df_filtrado = self.df.dropna(subset='Musica').sort_values(by='Duracao').tail(1)
48
+ return df_filtrado.Artista.values[0] + ' - ' + df_filtrado.Musica.values[0] + ' (' + df_filtrado.Duracao_Formatada.values[0] + ')'
49
+
50
+ def get_top_artista(self):
51
+ top_artista = (self.df.groupby('Artista')
52
+ .size()
53
+ .reset_index(name='Count')
54
+ .sort_values(by='Count', ascending=False))
55
+ top_artista = (top_artista[top_artista['Count'] == top_artista
56
+ .drop_duplicates(subset='Count')
57
+ .head(1)['Count']
58
+ .values[0]])
59
+ top_artista['Str'] = top_artista.Artista + ' (' + top_artista.Count.astype(str) + ')'
60
+ return ', '.join(top_artista.Str)
61
+
62
+ def get_top_album(self):
63
+ top_album = (self.df.groupby(['Album_Single', 'Artista'])
64
+ .size()
65
+ .reset_index(name='Count')
66
+ .sort_values(by='Count', ascending=False))
67
+ top_album = (top_album[top_album['Count'] == top_album
68
+ .drop_duplicates(subset='Count')
69
+ .head(1)['Count']
70
+ .values[0]])
71
+ top_album['Str'] = top_album.Artista + ' - ' + top_album.Album_Single + ' (' + top_album.Count.astype(str) + ')'
72
+ return ', '.join(top_album.Str)
73
+
74
+ def get_top_genero(self):
75
+ top_genero = (self.df.groupby('Genero')
76
+ .size()
77
+ .reset_index(name='Count')
78
+ .sort_values(by='Count', ascending=False))
79
+ top_genero = (top_genero[top_genero['Count'] == top_genero
80
+ .drop_duplicates(subset='Count')
81
+ .head(1)['Count']
82
+ .values[0]])
83
+ top_genero['Str'] = top_genero.Genero + ' (' + top_genero.Count.astype(str) + ')'
84
+ return ', '.join(top_genero.Str)
85
+
86
+ def get_repetidas(self):
87
+ df_repetidas = self.df[self.df['Observacao'] == 'repetida'].groupby('Observacao').size().reset_index(name='Count')
88
+ if df_repetidas.empty:
89
+ return 'Não'
90
+ else:
91
+ return 'Sim (' + str(df_repetidas.Count.values[0]) + ')'
92
+
93
+ def get_lista_paises(self):
94
+ return self.df.groupby(['Edicao', 'Pais']).size().reset_index(name='Quantidade')
95
+
96
+ def get_lista_generos(self):
97
+ return self.df.groupby(['Edicao', 'Genero']).size().reset_index(name='Quantidade')
98
+
99
+ def get_musicas(self):
100
+ df = self.df.sort_values(by='Data_Lancamento_Album')
101
+ df = df.loc[:, ['Data_Lancamento_Album', 'Artista', 'Musica']].reset_index()
102
+ df = df.rename(columns={"index": "unique_id", "Artista": "text", "Musica": "headline"})
103
+
104
+ df['year'] = df['Data_Lancamento_Album'].dt.year
105
+ df['month'] = df['Data_Lancamento_Album'].dt.month
106
+ df['day'] = df['Data_Lancamento_Album'].dt.day
107
+ df['start_date'] = df[['year', 'month', 'day']].to_dict(orient='records')
108
+ df['text'] = df[['headline', 'text']].to_dict(orient='records')
109
+ df = df.drop(['Data_Lancamento_Album','year', 'month', 'day', 'headline'], axis=1).to_dict(orient='records')
110
+
111
+ return json.dumps({'events': df})
112
+
113
+ def get_range_data_lancamento(self):
114
+ return [(self.df.Data_Lancamento_Album.min() + pd.DateOffset(years=-3)).strftime('%Y-%m-%dT%H:%M:%SZ'),
115
+ (self.df.Data_Lancamento_Album.max() + pd.DateOffset(years=3)).strftime('%Y-%m-%dT%H:%M:%SZ')]
116
+
117
+ class InfoMusica(InfoBase):
118
+
119
+ def __init__(self, df_data, id_musica):
120
+ musica = df_data[df_data['Id'] == id_musica].Musica
121
+ artista = df_data[df_data['Id'] == id_musica].Artista
122
+
123
+ self.df = df_data.loc[(df_data['Artista'] == artista.values[0]) & (df_data['Musica'] == musica.values[0])]
124
+
125
+ def get_numero_aparicoes(self):
126
+ return np.size(self.df.Posicao)
127
+
128
+ def get_numero_podios(self):
129
+ return np.size(self._listar_podios(self.df)['Musica'])
130
+
131
+ def get_decada(self):
132
+ return self.df.Decada_Lancamento_Album.values[0]
133
+
134
+ def get_posicao_media(self):
135
+ return np.mean(self.df.Posicao).round(0).astype(int)
136
+
137
+ def get_posicoes(self):
138
+ return self.df.sort_values(by='Ano', ascending=True)
139
+
140
+ class InfoArtista(InfoBase):
141
+
142
+ def __init__(self, df_data, artista):
143
+ self.df = df_data.loc[(df_data['Artista'] == artista)]
144
+
145
+ def get_total_musicas(self):
146
+ return np.size(self.df.Id)
147
+
148
+ def get_total_edicoes(self):
149
+ return np.size(self.df['Edicao'].drop_duplicates())
150
+
151
+ def get_media_musicas_por_edicao(self):
152
+ return (self.get_total_musicas()/self.get_total_edicoes())
153
+
154
+ def get_numero_aparicoes_consecutivas(self):
155
+ return self._contar_consecutivos(self.df['Ano'].drop_duplicates())
156
+
157
+ def get_numero_podios(self):
158
+ return np.size(self._listar_podios(self.df)['Artista'])
159
+
160
+ class InfoCuriosidade:
161
+
162
+ def __init__(self, df_data):
163
+ self.df = df_data
164
+
165
+ def __agrupar_dataframe(self, agregador):
166
+ return self.df.groupby(agregador).size().reset_index(name = 'Count')
167
+
168
+ def get_primeiro_artista_br(self):
169
+ df = self.df[self.df['Pais'] == 'Brasil'].sort_values(['Ano', 'Posicao'], ascending=False).tail(1)
170
+ return [df.Artista.values[0], df.Ano.values[0], df.Posicao.values[0]]
171
+
172
+ def get_edicao_menos_artistas(self):
173
+ df = self.__agrupar_dataframe(['Edicao', 'Artista']).groupby('Edicao')['Count'].count().reset_index().sort_values('Count')
174
+ return [df.head(1).Edicao.values[0], df.head(1).Count.values[0]]
175
+
176
+ def get_edicao_mais_artistas(self):
177
+ df = self.__agrupar_dataframe(['Edicao', 'Artista']).groupby('Edicao')['Count'].count().reset_index().sort_values('Count')
178
+ return [df.tail(1).Edicao.values[0], df.tail(1).Count.values[0]]
179
+
180
+ def get_album_mais_musicas(self):
181
+ df = self.__agrupar_dataframe(['Album_Single', 'Artista']).sort_values('Count').tail(1)
182
+ return [df.Artista.values[0], df.Album_Single.values[0], df.Count.values[0], np.round(df.Count.values[0]/len(self.df)*100,2)]
183
+
184
+ def get_artista_mais_musicas_edicao(self):
185
+ df = self.__agrupar_dataframe(['Edicao', 'Artista']).sort_values('Count').tail(1)
186
+ return [df.Artista.values[0], df.Count.values[0], df.Edicao.values[0]]
187
+
188
+ def get_one_hit_wonder(self):
189
+ df = self.df.drop_duplicates(subset=['Artista', 'Musica', 'Observacao']).groupby('Artista').count().reset_index()[['Artista', 'Ano']]
190
+ df.columns = ['Artista', 'Count']
191
+ one_hit_wonders = df[df['Count'] == 1].sort_values(by='Artista')
192
+ return [one_hit_wonders.shape[0], np.round(one_hit_wonders.shape[0]/df.shape[0]*100,2)]
193
+
194
+ def get_album_mais_musicas_edicao(self):
195
+ df = self.__agrupar_dataframe(['Edicao', 'Album_Single']).sort_values('Count').sort_values('Count').tail(1)
196
+ return [df.Album_Single.values[0], df.Count.values[0], df.Edicao.values[0]]
197
+
198
+ def get_artista_maior_percentual(self):
199
+ df = self.__agrupar_dataframe(['Artista']).sort_values('Count').tail(1)
200
+ return [df.Artista.values[0], df.Count.values[0], np.round(df.Count.values[0]/len(self.df)*100,2)]
201
+
202
+ def get_duracao(self):
203
+ df_filtrado = self.df.dropna(subset='Musica').sort_values(by='Duracao')
204
+ return [df_filtrado.head(1).Duracao_Formatada.values[0], df_filtrado.tail(1).Duracao_Formatada.values[0]]
notebooks/500-kissfm.ipynb ADDED
@@ -0,0 +1 @@
 
 
1
+ {"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":7630495,"sourceType":"datasetVersion","datasetId":4443244}],"dockerImageVersionId":30646,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sb\nimport locale\n\n#Configuração\n#locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')\n\nclass Info:\n \n def __init__(self, df_data, ano):\n self.df = df_data[df_data['Ano'] == ano]\n \n def get_musica_posicao(self, posicao):\n df_filtrado = self.df[self.df['Posicao'] == posicao]\n return df_filtrado.Artista.values[0] + ' - ' + df_filtrado.Musica.values[0]\n \n def get_top_artista(self):\n top_artista = (self.df.groupby('Artista')\n .size()\n .reset_index(name='Count')\n .sort_values(by='Count', ascending=False))\n top_artista = (top_artista[top_artista['Count'] == top_artista\n .drop_duplicates(subset='Count')\n .head(1)['Count']\n .values[0]])\n top_artista['Str'] = top_artista.Artista + ' (' + top_artista.Count.astype(str) + ')'\n return ', '.join(top_artista.Str)\n \n def get_top_album(self):\n top_album = (self.df.groupby(['Album_Single', 'Artista'])\n .size()\n .reset_index(name='Count')\n .sort_values(by='Count', ascending=False))\n top_album = (top_album[top_album['Count'] == top_album\n .drop_duplicates(subset='Count')\n .head(1)['Count']\n .values[0]])\n top_album['Str'] = top_album.Artista + ' - ' + top_album.Album_Single + ' (' + top_album.Count.astype(str) + ')'\n return ', '.join(top_album.Str)\n \n def get_repetidas(self):\n df_repetidas = self.df[self.df['Observacao'] == 'repetida'].groupby('Observacao').size().reset_index(name='Count')\n if df_repetidas.empty:\n return 'Não'\n else:\n return 'Sim (' + str(df_repetidas.Count.values[0]) + ')'\n\ndef get_decada(ano):\n return 'Anos ' + str(ano)[2] + '0'\n\n#Inicialização\ndf_listagem = pd.read_csv(\"/kaggle/input/500-kiss/500.csv\")\ndf_listagem['Id'] = range(1, len(df_listagem) + 1)\ndf_listagem['Ano_Periodo'] = df_listagem.Ano.astype(str).str[-2:] + \"-\" + (df_listagem.Ano +1).astype(str).str[-2:]\ndf_listagem['Data_Lancamento_Album'] = pd.to_datetime(df_listagem['Data_Lancamento_Album'])\ndf_listagem['Decada_Lancamento_Album'] = df_listagem['Data_Lancamento_Album'].dt.year.apply(get_decada)\n\n#Funções\ndef filtrar_periodo(df_data, periodo_inicial, periodo_final):\n periodos = np.unique(df_data.Ano_Periodo).tolist()\n indice_inicial = periodos.index(periodo_inicial)\n indice_final = periodos.index(periodo_final)+1\n periodos_selecionados = periodos[indice_inicial:indice_final]\n return df_data[df_data['Ano_Periodo'].isin(periodos_selecionados)]\n\ndef filtrar_posicoes(df_data, posicao_inicial, posicao_final):\n posicoes = list(range(posicao_inicial, posicao_final + 1))\n return df_data[df_data['Posicao'].isin(posicoes)]\n\ndef filtrar_inconsistencias(df_data):\n return df_data.loc[(df_data['Artista'] != '???') & (df_data['Musica'].str.len() > 0) & (df_data['Observacao'] != 'repetida')]\n\ndef get_primeiro_ano(df_data):\n return df_data.sort_values(by='Ano').head(1)['Ano']\n\ndef get_ultimo_ano(df_data):\n return df_data.sort_values(by='Ano').tail(1)['Ano']\n\ndef get_primeiro_ano_periodo(df_data):\n return df_data.sort_values(by='Ano').head(1)['Ano_Periodo']\n\ndef get_ultimo_ano_periodo(df_data):\n return df_data.sort_values(by='Ano').tail(1)['Ano_Periodo']\n\ndef get_primeiro_ano_lancamento(df_data):\n return df_listagem.dropna(subset=['Musica']).sort_values(by = 'Data_Lancamento_Album').head(1)['Data_Lancamento_Album'].dt.year\n\ndef get_ultimo_ano_lancamento(df_data):\n return df_listagem.dropna(subset=['Musica']).sort_values(by = 'Data_Lancamento_Album').tail(1)['Data_Lancamento_Album'].dt.year\n\ndef get_total_musicas_distintas(df_data):\n return len(get_musicas_distintas(df_data))\n\ndef get_musicas_distintas(df_data):\n return filtrar_inconsistencias(df_data).drop_duplicates(subset=['Artista', 'Musica', 'Observacao'])\n\ndef get_acumulado_musicas_distintas(df_data):\n periodos = np.unique(df_data.Ano_Periodo).tolist()\n distinta_acumulado_periodo = []\n for p in periodos:\n distinta_acumulado_periodo.append(get_total_musicas_distintas(filtrar_periodo(df_data, periodos[0], p)))\n return pd.DataFrame({'Anos': periodos, 'Acumulado': distinta_acumulado_periodo})\n\ndef get_musicas_ano_lancamento(df_data):\n df_temp = get_musicas_distintas(df_data)\n return pd.DataFrame(df_temp.groupby(df_temp['Data_Lancamento_Album'].dt.year).size().reset_index().rename(columns={0: 'Total_Musicas'}))\n\ndef get_musicas_decada_lancamento(df_data):\n df_temp = get_musicas_distintas(df_data)\n df_temp['Total_Musicas'] = df_temp.groupby('Decada_Lancamento_Album')['Decada_Lancamento_Album'].transform('count')\n return pd.DataFrame(df_temp.sort_values('Data_Lancamento_Album').groupby(['Decada_Lancamento_Album', 'Total_Musicas']).head(1))[['Decada_Lancamento_Album', 'Total_Musicas']]\n\ndef get_musicas_todos_anos(df_data):\n df = filtrar_inconsistencias(df_data).copy()\n df['Count'] = df.groupby(['Artista', 'Musica', 'Observacao'], dropna=False)['Musica'].transform('count')\n df['Musica'] = df.apply(lambda row: row['Artista'] + ' - ' + row['Musica'], axis=1)\n df = df.loc[df['Count'] == 24].sort_values(['Ano','Posicao'])\n\n return pd.pivot(data=df, index='Musica', columns='Ano_Periodo', values='Posicao')\n\ndef get_musicas_media_posicao(df_data):\n #Fórmula Si = wi * Ai + (1 - wi) * S, em que:\n #wi = mi/mi+m_avg, sendo mi número total de aparições da música e m_avg média de todas as aparições de músicas\n #Ai = média aritmética da posição da música\n #S = média aritmética da posição de todas as músicas\n #Si = média bayesiana da posição da música\n #https://arpitbhayani.me/blogs/bayesian-average/\n \n df_distintas = filtrar_inconsistencias(df_data.copy())\n \n #Workaround devido a problema de index com NaN no pivot_table. Necessário preencher o que está NaN com um valor dummy para poder fazer o grouping\n #https://github.com/pandas-dev/pandas/issues/3729\n df_distintas['Observacao'] = df_distintas['Observacao'].fillna('dummy')\n \n df_totalizador = (df_distintas\n .groupby(['Artista', 'Musica', 'Observacao'], dropna=False)\n .size()\n .reset_index(name='Total_Aparicoes'))\n\n m_avg = df_totalizador['Total_Aparicoes'].mean()\n \n pivot_table = (pd.pivot_table(df_distintas, \n index=['Artista', 'Musica', 'Observacao'], \n columns='Ano', \n values='Posicao', \n margins=True, \n margins_name = 'Media_Posicao'))\n \n S = pivot_table.loc[('Media_Posicao', '', ''), 'Media_Posicao']\n\n newdf = (df_distintas\n .groupby(['Artista', 'Musica', 'Observacao'], dropna=False)\n .size()\n .reset_index(name='Total_Aparicoes'))\n \n merged_df = pd.merge(df_totalizador, pivot_table, on = ['Artista', 'Musica', 'Observacao'])\n\n merged_df['Media_Bayesiana_Posicao'] = get_bayesian_average(merged_df['Total_Aparicoes'], m_avg, merged_df['Media_Posicao'], S)\n \n return merged_df.sort_values('Media_Bayesiana_Posicao')\n\ndef get_bayesian_average(m, m_avg, A, S):\n w = m/(m+m_avg)\n return w * A + (1-w) * S\n\ndef get_artistas_top_n(df_data, top_n):\n df = filtrar_posicoes(df_data, 1, top_n)\n df = (filtrar_inconsistencias(df)\n .groupby('Artista')\n .size()\n .sort_values(ascending=False)\n .reset_index(name='Total_Aparicoes'))\n return df\n\ndef get_musicas_top_n(df_data, top_n):\n df = filtrar_posicoes(df_data, 1, top_n)\n df = (filtrar_inconsistencias(df)\n .groupby('Musica')\n .size()\n .sort_values(ascending=False)\n .reset_index(name='Total_Aparicoes'))\n return df\n\ndef get_analise_periodo(df_data, medida, agregador):\n df = filtrar_inconsistencias(df_data)\n df = df.groupby(agregador)['Musica'].count().reset_index()\n match medida:\n case 'Contagem':\n return df.groupby('Ano_Periodo').sum().reset_index()\n case 'Média':\n return df.groupby('Ano_Periodo')['Musica'].mean().reset_index()\n case 'Mediana':\n return df.groupby('Ano_Periodo')['Musica'].median().reset_index()\n case 'Mínimo':\n return df.groupby('Ano_Periodo')['Musica'].min().reset_index()\n case 'Máximo':\n return df.groupby('Ano_Periodo')['Musica'].max().reset_index()\n case default:\n return df\n\ndef plotar_grafico_barra(df_data, xdata, ydata, xlabel, ylabel, decimal=False, rotacao=0):\n rc = {'figure.figsize':(12,4.5),\n 'axes.facecolor':'#0e1117',\n 'axes.edgecolor': '#0e1117',\n 'axes.labelcolor': 'white',\n 'figure.facecolor': '#0e1117',\n 'patch.edgecolor': '#0e1117',\n 'text.color': 'white',\n 'xtick.color': 'white',\n 'ytick.color': 'white',\n 'grid.color': 'grey',\n 'font.size' : 8,\n 'axes.labelsize': 12,\n 'xtick.labelsize': 8,\n 'ytick.labelsize': 12}\n\n plt.rcParams.update(rc)\n fig, ax = plt.subplots()\n\n ax = sb.barplot(x=xdata, y=ydata, data=df_data, color = \"#b80606\")\n ax.set(xlabel = xlabel, ylabel = ylabel)\n plt.xticks(rotation=66,horizontalalignment=\"right\")\n for p in ax.patches:\n if decimal:\n text = format(p.get_height(), '.2f')\n else:\n text = format(str(int(p.get_height())))\n ax.annotate(text,\n (p.get_x() + p.get_width() / 2., p.get_height()),\n ha = 'center',\n va = 'center',\n xytext = (0, 18),\n rotation = rotacao,\n textcoords = 'offset points')\n plt.show()\n \ndef plotar_mapa_calor(df_data):\n plt.figure(figsize=(20,9.5))\n plt.tick_params(axis='both', which='major', labelsize=10, labelbottom = True, bottom=True, top = True, labeltop=True)\n sb.heatmap(df_data, cmap='viridis_r', annot=True, cbar=False, fmt='g')\n plt.show()\n\n# App\ndf_listagem_filtrada = filtrar_periodo(df_listagem, '00-01', '23-24')\ndf_listagem_filtrada = filtrar_posicoes(df_listagem_filtrada, 1, 500)\n\ntotal_musicas = df_listagem_filtrada.Id.nunique()\ntotal_musicas_distintas = get_total_musicas_distintas(df_listagem_filtrada)\ntotal_artistas = len(np.unique(df_listagem_filtrada.Artista.dropna()).tolist())\ntotal_albuns = len(np.unique(df_listagem_filtrada.Album_Single.dropna().astype(str)).tolist())\n\nstr_total_musicas = \"🎶 \" + locale.format_string(\"%d\", total_musicas, grouping = True) + \" músicas no total\"\nstr_total_musicas_distintas = \"🎵 \" + locale.format_string(\"%d\", total_musicas_distintas, grouping = True) + \" músicas diferentes\"\nstr_total_artistas = \"🧑‍🎤 \" + locale.format_string(\"%d\", total_artistas, grouping = True) + \" artista(s)\"\nstr_total_albuns = \"💿 \" + locale.format_string(\"%d\", total_albuns, grouping = True) + \" álbum(s)/single(s)\"\n\nprint(str_total_musicas, str_total_musicas_distintas, str_total_artistas, str_total_albuns, \"\\n\")\n\nget_musicas_media_posicao(df_listagem_filtrada)\n\nget_artistas_top_n(df_listagem, 3)\nget_artistas_top_n(df_listagem, 10)\n\nget_musicas_top_n(df_listagem, 3)\nget_musicas_top_n(df_listagem, 10)\n\ninfo = Info(df_listagem, 2000)\ninfo.get_musica_posicao(1)\ninfo.get_musica_posicao(500)\ninfo.get_top_artista()\ninfo.get_repetidas()\ninfo.get_top_album()\n\nplotar_grafico_barra(get_acumulado_musicas_distintas(df_listagem_filtrada), \"Anos\", \"Acumulado\", \"Anos\", \"Acumulado de Músicas distintas\")\n\nplotar_grafico_barra(get_musicas_ano_lancamento(df_listagem_filtrada), \"Data_Lancamento_Album\", \"Total_Musicas\", \"Anos\", \"Quantidade de Músicas distintas\")\n\nplotar_grafico_barra(get_musicas_decada_lancamento(df_listagem_filtrada), \"Decada_Lancamento_Album\", \"Total_Musicas\", \"Décadas\", \"Quantidade de Músicas distintas\")\n\nplotar_grafico_barra(get_analise_periodo(df_listagem_filtrada, \"Média\", ['Artista', 'Ano_Periodo']), \"Ano_Periodo\", \"Musica\", \"Anos\", \"Músicas por Artista\", True)\n\nplotar_grafico_barra(get_analise_periodo(df_listagem_filtrada, 'Média', ['Album_Single', 'Ano_Periodo']), \"Ano_Periodo\", \"Musica\", \"Anos\", \"Álbuns por Artista\", True)\n\nplotar_mapa_calor(get_musicas_todos_anos(df_listagem))","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}
notebooks/predicao_proximo_ano.ipynb ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "8d71a4a8",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 1. Configurações e carregamento do dataset"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "62be5d5b",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import pandas as pd\n",
19
+ "import numpy as np\n",
20
+ "from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor\n",
21
+ "import warnings\n",
22
+ "warnings.filterwarnings('ignore')\n",
23
+ "\n",
24
+ "dataset_file = '../data/500+.csv'\n",
25
+ "ano_predicao = 2025\n",
26
+ "\n",
27
+ "def filtrar_inconsistencias(df_data):\n",
28
+ " return df_data.loc[(df_data['Artista'] != '???') & (df_data['Musica'].str.len() > 0) & (df_data['Observacao'] != 'repetida')]\n",
29
+ "\n",
30
+ "def load_data(agregar_pinkfloyd):\n",
31
+ " df_data = pd.read_csv(dataset_file)\n",
32
+ " df_data['Data_Lancamento_Album'] = pd.to_datetime(df_data['Data_Lancamento_Album'])\n",
33
+ " df_data['Decada_Musica'] = (df_data['Data_Lancamento_Album'].dt.year // 10) * 10\n",
34
+ " df_data['Ano_Musica'] = df_data['Data_Lancamento_Album'].dt.year\n",
35
+ " df_data['Duracao'] = df_data.loc[:,'Duracao'].fillna(value=0)\n",
36
+ " if (agregar_pinkfloyd):\n",
37
+ " df_data.loc[df_data['Musica'].str.contains('Another Brick', na=False), 'Musica'] = 'Another Brick in the Wall'\n",
38
+ " df_data.loc[df_data['Musica'].str.contains('Another Brick', na=False), 'Duracao'] = 508\n",
39
+ "\n",
40
+ " df_data = df_data.drop(['Artista_Origem', 'Musica_Origem', 'Artista_Wikidata_ID', 'Artista_Wikidata', 'Artista_Wiki', 'Country', 'Genre', 'Musica_Wikidata_ID', 'Musica_Wikidata', 'Musica_Wiki', 'Album_Single_Wikidata_ID', 'Album_Single_Wikidata', 'Album_Single_Wiki', 'Data_Lancamento_Album'], axis=1)\n",
41
+ " df_data.rename(columns={'Album_Single':'Album'}, inplace=True)\n",
42
+ " df_data = filtrar_inconsistencias(df_data)\n",
43
+ " return df_data\n",
44
+ "\n",
45
+ "df = load_data(True)\n",
46
+ "df = df[df['Ano'] < ano_predicao]\n",
47
+ "print(\"Dataset carregado com sucesso!\")"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "markdown",
52
+ "id": "c8044620",
53
+ "metadata": {},
54
+ "source": [
55
+ "## 2. Identificador única da música"
56
+ ]
57
+ },
58
+ {
59
+ "cell_type": "code",
60
+ "execution_count": null,
61
+ "id": "74bfdb24",
62
+ "metadata": {},
63
+ "outputs": [],
64
+ "source": [
65
+ "print(\"Preparando dados...\")\n",
66
+ "\n",
67
+ "# Criar identificador único: Artista-Musica-Observacao\n",
68
+ "df['id_musica'] = (df['Artista'].fillna('') + '|||' + \n",
69
+ " df['Musica'].fillna('') + '|||' + \n",
70
+ " df['Observacao'].fillna(''))\n",
71
+ "\n",
72
+ "# Garantir que Observacao seja tratada corretamente\n",
73
+ "df['Observacao'] = df['Observacao'].fillna('')\n",
74
+ "\n",
75
+ "print(f\"\\nTotal de músicas únicas: {df['id_musica'].nunique()}\")\n",
76
+ "print(f\"Total de registros: {len(df)}\")\n",
77
+ "\n",
78
+ "print(f\"\\nAno de previsão: {ano_predicao}\")\n",
79
+ "print(f\"Anos no dataset: {df['Ano'].min()} a {df['Ano'].max()}\")"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "markdown",
84
+ "id": "0d49161e",
85
+ "metadata": {},
86
+ "source": [
87
+ "## 3. Engenharia de features"
88
+ ]
89
+ },
90
+ {
91
+ "cell_type": "code",
92
+ "execution_count": null,
93
+ "id": "ec4f2c92",
94
+ "metadata": {},
95
+ "outputs": [],
96
+ "source": [
97
+ "def calcular_features_musica(df, ano_previsao):\n",
98
+ " features_list = []\n",
99
+ " \n",
100
+ " # Obter todas as músicas únicas\n",
101
+ " musicas_unicas = df['id_musica'].unique()\n",
102
+ " \n",
103
+ " for id_musica in musicas_unicas:\n",
104
+ " df_musica = df[df['id_musica'] == id_musica].sort_values('Ano')\n",
105
+ " \n",
106
+ " # Informações básicas\n",
107
+ " artista = df_musica['Artista'].iloc[0]\n",
108
+ " musica = df_musica['Musica'].iloc[0]\n",
109
+ " observacao = df_musica['Observacao'].iloc[0]\n",
110
+ " pais = df_musica['Pais'].iloc[0] if 'Pais' in df_musica.columns else None\n",
111
+ " genero = df_musica['Genero'].iloc[0] if 'Genero' in df_musica.columns else None\n",
112
+ " \n",
113
+ " # Anos de aparição\n",
114
+ " anos_aparicao = df_musica['Ano'].values\n",
115
+ " anos_totais = ano_previsao - df['Ano'].min()\n",
116
+ " \n",
117
+ " # 1. frequencia_aparicao: % de anos em que apareceu\n",
118
+ " frequencia_aparicao = len(anos_aparicao) / anos_totais if anos_totais > 0 else 0\n",
119
+ " \n",
120
+ " # 2. streak_anos: Anos consecutivos (até o último ano)\n",
121
+ " anos_ordenados = sorted(anos_aparicao, reverse=True)\n",
122
+ " streak_anos = 0\n",
123
+ " ano_esperado = ano_previsao - 1\n",
124
+ " for ano in anos_ordenados:\n",
125
+ " if ano == ano_esperado:\n",
126
+ " streak_anos += 1\n",
127
+ " ano_esperado -= 1\n",
128
+ " else:\n",
129
+ " break\n",
130
+ " \n",
131
+ " # 3. anos_desde_ultima: Anos desde última aparição\n",
132
+ " ultimo_ano = max(anos_aparicao)\n",
133
+ " anos_desde_ultima = ano_previsao - ultimo_ano - 1\n",
134
+ " \n",
135
+ " # 4. aparicao_unica: Flag binária\n",
136
+ " aparicao_unica = 1 if len(anos_aparicao) == 1 else 0\n",
137
+ " \n",
138
+ " # 5. anos_desde_unica_aparicao\n",
139
+ " anos_desde_unica_aparicao = anos_desde_ultima if aparicao_unica == 1 else 0\n",
140
+ " \n",
141
+ " # 6. dropout_score: Score de risco de dropout\n",
142
+ " # Maior quando: aparição única antiga, ou muitos anos sem aparecer\n",
143
+ " if aparicao_unica == 1:\n",
144
+ " dropout_score = min(anos_desde_unica_aparicao / 10, 1.0)\n",
145
+ " else:\n",
146
+ " dropout_score = min(anos_desde_ultima / 5, 1.0) * (1 - frequencia_aparicao)\n",
147
+ " \n",
148
+ " # 7. forca_musica: Score composto de estabelecimento\n",
149
+ " # Maior quanto mais frequente e recente\n",
150
+ " forca_musica = (frequencia_aparicao * 0.5 + \n",
151
+ " (1 - min(anos_desde_ultima / 10, 1.0)) * 0.3 +\n",
152
+ " min(streak_anos / 5, 1.0) * 0.2)\n",
153
+ " \n",
154
+ " # 8. penalidade_one_hit: Penalidade para one-hit wonders\n",
155
+ " penalidade_one_hit = anos_desde_unica_aparicao * 0.1 if aparicao_unica == 1 else 0\n",
156
+ " \n",
157
+ " # 9. volatilidade_posicao: Amplitude entre melhor e pior posição\n",
158
+ " posicoes = df_musica['Posicao'].values\n",
159
+ " volatilidade_posicao = max(posicoes) - min(posicoes) if len(posicoes) > 1 else 0\n",
160
+ " \n",
161
+ " # 10. consistencia: Regularidade nas aparições\n",
162
+ " if len(anos_aparicao) > 1:\n",
163
+ " gaps = np.diff(sorted(anos_aparicao))\n",
164
+ " consistencia = 1 / (1 + np.std(gaps)) if len(gaps) > 0 else 1\n",
165
+ " else:\n",
166
+ " consistencia = 0\n",
167
+ " \n",
168
+ " # Estatísticas de posição\n",
169
+ " posicao_media = df_musica['Posicao'].mean()\n",
170
+ " melhor_posicao = df_musica['Posicao'].min()\n",
171
+ " pior_posicao = df_musica['Posicao'].max()\n",
172
+ " ultima_posicao = df_musica[df_musica['Ano'] == ultimo_ano]['Posicao'].iloc[0]\n",
173
+ " \n",
174
+ " # Tendência de posição (melhorando ou piorando)\n",
175
+ " if len(posicoes) > 1:\n",
176
+ " tendencia_posicao = posicoes[-1] - posicoes[0] # negativo = melhorando\n",
177
+ " else:\n",
178
+ " tendencia_posicao = 0\n",
179
+ " \n",
180
+ " features_list.append({\n",
181
+ " 'id_musica': id_musica,\n",
182
+ " 'Artista': artista,\n",
183
+ " 'Musica': musica,\n",
184
+ " 'Observacao': observacao,\n",
185
+ " 'Pais': pais,\n",
186
+ " 'Genero': genero,\n",
187
+ " 'frequencia_aparicao': frequencia_aparicao,\n",
188
+ " 'streak_anos': streak_anos,\n",
189
+ " 'anos_desde_ultima': anos_desde_ultima,\n",
190
+ " 'aparicao_unica': aparicao_unica,\n",
191
+ " 'anos_desde_unica_aparicao': anos_desde_unica_aparicao,\n",
192
+ " 'dropout_score': dropout_score,\n",
193
+ " 'forca_musica': forca_musica,\n",
194
+ " 'penalidade_one_hit': penalidade_one_hit,\n",
195
+ " 'volatilidade_posicao': volatilidade_posicao,\n",
196
+ " 'consistencia': consistencia,\n",
197
+ " 'posicao_media': posicao_media,\n",
198
+ " 'melhor_posicao': melhor_posicao,\n",
199
+ " 'pior_posicao': pior_posicao,\n",
200
+ " 'ultima_posicao': ultima_posicao,\n",
201
+ " 'tendencia_posicao': tendencia_posicao,\n",
202
+ " 'num_aparicoes': len(anos_aparicao),\n",
203
+ " 'ultimo_ano': ultimo_ano\n",
204
+ " })\n",
205
+ " \n",
206
+ " return pd.DataFrame(features_list)"
207
+ ]
208
+ },
209
+ {
210
+ "cell_type": "markdown",
211
+ "id": "622257a7",
212
+ "metadata": {},
213
+ "source": [
214
+ "## 4. Preparação dos dados de treino"
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": null,
220
+ "id": "7473753a",
221
+ "metadata": {},
222
+ "outputs": [],
223
+ "source": [
224
+ "print(\"Preparando dados de treino...\")\n",
225
+ "\n",
226
+ "anos = sorted(df['Ano'].unique())\n",
227
+ "\n",
228
+ "dados_treino = []\n",
229
+ "\n",
230
+ "for i in range(len(anos) - 1):\n",
231
+ " ano_atual = anos[i]\n",
232
+ " ano_proximo = anos[i + 1]\n",
233
+ " \n",
234
+ " # Features até o ano atual\n",
235
+ " df_ate_ano = df[df['Ano'] <= ano_atual]\n",
236
+ " features_ano = calcular_features_musica(df_ate_ano, ano_proximo)\n",
237
+ " \n",
238
+ " # Target: posição no próximo ano (ou 501 se não apareceu)\n",
239
+ " df_proximo_ano = df[df['Ano'] == ano_proximo]\n",
240
+ " \n",
241
+ " for _, row in features_ano.iterrows():\n",
242
+ " id_musica = row['id_musica']\n",
243
+ " \n",
244
+ " # Verificar se apareceu no próximo ano\n",
245
+ " musica_proximo = df_proximo_ano[df_proximo_ano['id_musica'] == id_musica]\n",
246
+ " \n",
247
+ " if len(musica_proximo) > 0:\n",
248
+ " posicao_proxima = musica_proximo['Posicao'].iloc[0]\n",
249
+ " apareceu = 1\n",
250
+ " else:\n",
251
+ " posicao_proxima = 501 # Não apareceu\n",
252
+ " apareceu = 0\n",
253
+ " \n",
254
+ " dados_treino.append({\n",
255
+ " **row.to_dict(),\n",
256
+ " 'ano_previsao': ano_proximo,\n",
257
+ " 'posicao_proxima': posicao_proxima,\n",
258
+ " 'apareceu_proximo': apareceu\n",
259
+ " })\n",
260
+ " \n",
261
+ " df_treino = pd.DataFrame(dados_treino)\n",
262
+ "\n",
263
+ " print(f\"Exemplos de treino: {len(df_treino)}\")"
264
+ ]
265
+ },
266
+ {
267
+ "cell_type": "markdown",
268
+ "id": "16cc4b8f",
269
+ "metadata": {},
270
+ "source": [
271
+ "## 5. Treinamento do modelo"
272
+ ]
273
+ },
274
+ {
275
+ "cell_type": "code",
276
+ "execution_count": null,
277
+ "id": "eddf47c8",
278
+ "metadata": {},
279
+ "outputs": [],
280
+ "source": [
281
+ "print(\"Treinando modelos...\")\n",
282
+ "\n",
283
+ "# Features para o modelo\n",
284
+ "feature_cols = [\n",
285
+ " 'frequencia_aparicao', 'streak_anos', 'anos_desde_ultima',\n",
286
+ " 'aparicao_unica', 'anos_desde_unica_aparicao', 'dropout_score',\n",
287
+ " 'forca_musica', 'penalidade_one_hit', 'volatilidade_posicao',\n",
288
+ " 'consistencia', 'posicao_media', 'melhor_posicao', \n",
289
+ " 'ultima_posicao', 'tendencia_posicao', 'num_aparicoes'\n",
290
+ "]\n",
291
+ "\n",
292
+ "X = df_treino[feature_cols].fillna(0)\n",
293
+ "\n",
294
+ "# Modelo 1: Prever se vai aparecer (classificação binária)\n",
295
+ "y_aparece = df_treino['apareceu_proximo']\n",
296
+ "\n",
297
+ "# Modelo 2: Prever posição (apenas para músicas que aparecem)\n",
298
+ "df_apareceu = df_treino[df_treino['apareceu_proximo'] == 1]\n",
299
+ "X_pos = df_apareceu[feature_cols].fillna(0)\n",
300
+ "y_pos = df_apareceu['posicao_proxima']\n",
301
+ "\n",
302
+ "# Treinar modelos\n",
303
+ "print(\"Treinando modelo de aparição...\")\n",
304
+ "modelo_aparicao = GradientBoostingRegressor(n_estimators=200, max_depth=5, random_state=42)\n",
305
+ "modelo_aparicao.fit(X, y_aparece)\n",
306
+ "\n",
307
+ "print(\"Treinando modelo de posição...\")\n",
308
+ "modelo_posicao = RandomForestRegressor(n_estimators=200, max_depth=15, random_state=42)\n",
309
+ "modelo_posicao.fit(X_pos, y_pos)\n",
310
+ "\n",
311
+ "# Importância das features\n",
312
+ "print(\"\\nImportância das Features (Aparição):\")\n",
313
+ "importancias = pd.DataFrame({\n",
314
+ " 'feature': feature_cols,\n",
315
+ " 'importancia': modelo_aparicao.feature_importances_\n",
316
+ "}).sort_values('importancia', ascending=False)\n",
317
+ "print(importancias.head(10))"
318
+ ]
319
+ },
320
+ {
321
+ "cell_type": "markdown",
322
+ "id": "e8d44f97",
323
+ "metadata": {},
324
+ "source": [
325
+ "## 6. Geração do ranking"
326
+ ]
327
+ },
328
+ {
329
+ "cell_type": "code",
330
+ "execution_count": null,
331
+ "id": "5f52a882",
332
+ "metadata": {},
333
+ "outputs": [],
334
+ "source": [
335
+ "# =============================================================================\n",
336
+ "# 6. GERAÇÃO DO RANKING PREVISTO\n",
337
+ "# =============================================================================\n",
338
+ "print(f\"Gerando ranking previsto para {ano_predicao}...\")\n",
339
+ "\n",
340
+ "# Calcular features para todas as músicas\n",
341
+ "features_atual = calcular_features_musica(df, ano_predicao)\n",
342
+ "\n",
343
+ "X = features_atual[feature_cols].fillna(0)\n",
344
+ "\n",
345
+ "# Prever probabilidade de aparecer\n",
346
+ "prob_aparecer = modelo_aparicao.predict(X)\n",
347
+ "prob_aparecer = np.clip(prob_aparecer, 0, 1)\n",
348
+ "\n",
349
+ "# Prever posição\n",
350
+ "posicao_prevista = modelo_posicao.predict(X)\n",
351
+ "posicao_prevista = np.clip(posicao_prevista, 1, 500)\n",
352
+ "\n",
353
+ "# Combinar resultados\n",
354
+ "features_atual['prob_aparecer'] = prob_aparecer\n",
355
+ "features_atual['posicao_prevista'] = posicao_prevista\n",
356
+ "\n",
357
+ "# Score combinado: músicas com maior probabilidade e melhor posição\n",
358
+ "features_atual['score_final'] = (\n",
359
+ " features_atual['prob_aparecer'] * 100 - \n",
360
+ " features_atual['posicao_prevista'] * 0.1\n",
361
+ ")\n",
362
+ "\n",
363
+ "# Ordenar por score\n",
364
+ "ranking = features_atual.sort_values('score_final', ascending=False).copy()\n",
365
+ "\n",
366
+ "# Adicionar posição no ranking\n",
367
+ "ranking['posicao_ranking'] = range(1, len(ranking) + 1)\n",
368
+ "\n",
369
+ "# Normalizar probabilidades para que somem 100% em cada posição\n",
370
+ "# (probabilidade de estar exatamente naquela posição)\n",
371
+ "ranking['prob_posicao_exata'] = ranking['prob_aparecer'] / ranking['prob_aparecer'].sum()\n",
372
+ "ranking['prob_posicao_exata'] = ranking['prob_posicao_exata'] * 100\n",
373
+ "\n",
374
+ "print(\"\\n\" + \"=\" * 80)\n",
375
+ "print(f\"RANKING PREVISTO PARA {ano_predicao}\")\n",
376
+ "print(\"=\" * 80)\n",
377
+ "print(\"\\nTop 20 músicas:\")\n",
378
+ "print(ranking.head(20).to_string(index=False))"
379
+ ]
380
+ },
381
+ {
382
+ "cell_type": "markdown",
383
+ "id": "f6802450",
384
+ "metadata": {},
385
+ "source": [
386
+ "## 7. Visualização"
387
+ ]
388
+ },
389
+ {
390
+ "cell_type": "code",
391
+ "execution_count": null,
392
+ "id": "68e182ca",
393
+ "metadata": {},
394
+ "outputs": [],
395
+ "source": [
396
+ "print(\"\\n\" + \"=\" * 80)\n",
397
+ "print(\"ANÁLISE DOS RESULTADOS\")\n",
398
+ "print(\"=\" * 80)\n",
399
+ "\n",
400
+ "print(f\"\\nTotal de músicas no ranking: {len(ranking)}\")\n",
401
+ "print(f\"\\nProbabilidade média de aparecer: {ranking['prob_aparecer'].mean():.2%}\")\n",
402
+ "print(f\"Força média das músicas: {ranking['forca_musica'].mean():.3f}\")\n",
403
+ "print(f\"Dropout score médio: {ranking['dropout_score'].mean():.3f}\")\n",
404
+ "\n",
405
+ "print(\"\\nDistribuição de Streak Anos:\")\n",
406
+ "print(ranking['streak_anos'].value_counts().sort_index().head(10))\n",
407
+ "\n",
408
+ "print(\"\\nMúsicas com maior probabilidade de aparecer:\")\n",
409
+ "print(ranking.nlargest(10, 'prob_aparecer')[\n",
410
+ " ['posicao_ranking', 'Artista', 'Musica', 'prob_aparecer', 'forca_musica']\n",
411
+ "].to_string(index=False))\n",
412
+ "\n",
413
+ "print(\"\\nMúsicas em risco (maior dropout_score):\")\n",
414
+ "print(ranking.nlargest(10, 'dropout_score')[\n",
415
+ " ['posicao_ranking', 'Artista', 'Musica', 'dropout_score', 'streak_anos']\n",
416
+ "].to_string(index=False))"
417
+ ]
418
+ },
419
+ {
420
+ "cell_type": "markdown",
421
+ "id": "4c0f219b",
422
+ "metadata": {},
423
+ "source": [
424
+ "## 8. Exportação"
425
+ ]
426
+ },
427
+ {
428
+ "cell_type": "code",
429
+ "execution_count": null,
430
+ "id": "6aeee619",
431
+ "metadata": {},
432
+ "outputs": [],
433
+ "source": [
434
+ "ranking = ranking[['posicao_ranking', 'Artista', 'Musica', 'Observacao', \n",
435
+ " 'prob_aparecer', 'prob_posicao_exata', 'posicao_prevista',\n",
436
+ " 'forca_musica', 'dropout_score', 'streak_anos']]\n",
437
+ "ranking.to_csv(\"../data/predicao_proximo_ano.csv\", index=False)"
438
+ ]
439
+ }
440
+ ],
441
+ "metadata": {
442
+ "kernelspec": {
443
+ "display_name": "Python 3",
444
+ "language": "python",
445
+ "name": "python3"
446
+ },
447
+ "language_info": {
448
+ "codemirror_mode": {
449
+ "name": "ipython",
450
+ "version": 3
451
+ },
452
+ "file_extension": ".py",
453
+ "mimetype": "text/x-python",
454
+ "name": "python",
455
+ "nbconvert_exporter": "python",
456
+ "pygments_lexer": "ipython3",
457
+ "version": "3.12.1"
458
+ }
459
+ },
460
+ "nbformat": 4,
461
+ "nbformat_minor": 5
462
+ }
notebooks/probabilidades_proximo_ano.ipynb ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "1227c753",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 1. Configurações e carregamento do dataset"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "bd199811",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "import pandas as pd\n",
19
+ "import numpy as np\n",
20
+ "import matplotlib.pyplot as plt\n",
21
+ "from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor\n",
22
+ "\n",
23
+ "import warnings\n",
24
+ "warnings.filterwarnings('ignore')\n",
25
+ "\n",
26
+ "dataset_file = '../data/500+.csv'\n",
27
+ "ano_predicao = 2025\n",
28
+ "\n",
29
+ "def filtrar_inconsistencias(df_data):\n",
30
+ " return df_data.loc[(df_data['Artista'] != '???') & (df_data['Musica'].str.len() > 0) & (df_data['Observacao'] != 'repetida')]\n",
31
+ "\n",
32
+ "def load_data(agregar_pinkfloyd):\n",
33
+ " df_data = pd.read_csv(dataset_file)\n",
34
+ " df_data['Data_Lancamento_Album'] = pd.to_datetime(df_data['Data_Lancamento_Album'])\n",
35
+ " df_data['Decada_Musica'] = (df_data['Data_Lancamento_Album'].dt.year // 10) * 10\n",
36
+ " df_data['Ano_Musica'] = df_data['Data_Lancamento_Album'].dt.year\n",
37
+ " df_data['Duracao'] = df_data.loc[:,'Duracao'].fillna(value=0)\n",
38
+ " if (agregar_pinkfloyd):\n",
39
+ " df_data.loc[df_data['Musica'].str.contains('Another Brick', na=False), 'Musica'] = 'Another Brick in the Wall'\n",
40
+ " df_data.loc[df_data['Musica'].str.contains('Another Brick', na=False), 'Duracao'] = 508\n",
41
+ "\n",
42
+ " df_data = df_data.drop(['Artista_Origem', 'Musica_Origem', 'Artista_Wikidata_ID', 'Artista_Wikidata', 'Artista_Wiki', 'Country', 'Genre', 'Musica_Wikidata_ID', 'Musica_Wikidata', 'Musica_Wiki', 'Album_Single_Wikidata_ID', 'Album_Single_Wikidata', 'Album_Single_Wiki', 'Data_Lancamento_Album'], axis=1)\n",
43
+ " df_data.rename(columns={'Album_Single':'Album'}, inplace=True)\n",
44
+ " df_data = filtrar_inconsistencias(df_data)\n",
45
+ " return df_data\n",
46
+ "\n",
47
+ "\n",
48
+ "df = load_data(True)\n",
49
+ "df = df[df['Ano'] < ano_predicao]\n",
50
+ "print(\"Dataset carregado com sucesso!\")"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "markdown",
55
+ "id": "4f8aed19",
56
+ "metadata": {},
57
+ "source": [
58
+ "## 2. Identificador única da música"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": null,
64
+ "id": "ac64cd77",
65
+ "metadata": {},
66
+ "outputs": [],
67
+ "source": [
68
+ "# Criar identificador único baseado na tríplice Artista-Musica-Observacao\n",
69
+ "df['Observacao_filled'] = df['Observacao'].fillna('NONE')\n",
70
+ "df['ID_Musica'] = df['Artista'] + '|||' + df['Musica'] + '|||' + df['Observacao_filled']\n",
71
+ "\n",
72
+ "print(f\"\\nTotal de músicas únicas: {df['ID_Musica'].nunique()}\")\n",
73
+ "print(f\"Total de registros: {len(df)}\")"
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "markdown",
78
+ "id": "f4075053",
79
+ "metadata": {},
80
+ "source": [
81
+ "## 3. Engenharia de features temporais"
82
+ ]
83
+ },
84
+ {
85
+ "cell_type": "code",
86
+ "execution_count": null,
87
+ "id": "d1346d8b",
88
+ "metadata": {},
89
+ "outputs": [],
90
+ "source": [
91
+ "df_sorted = df.sort_values(['ID_Musica', 'Ano'])\n",
92
+ "features_list = []\n",
93
+ "\n",
94
+ "print(\"Criando features históricas...\")\n",
95
+ "\n",
96
+ "for musica_id in df['ID_Musica'].unique():\n",
97
+ " df_musica = df_sorted[df_sorted['ID_Musica'] == musica_id].copy()\n",
98
+ " \n",
99
+ " for idx, row in df_musica.iterrows():\n",
100
+ " ano_atual = row['Ano']\n",
101
+ " historico = df_musica[df_musica['Ano'] < ano_atual]\n",
102
+ " \n",
103
+ " features = {\n",
104
+ " 'ID_Musica': musica_id,\n",
105
+ " 'Ano': ano_atual,\n",
106
+ " 'Posicao': row['Posicao'],\n",
107
+ " 'Artista': row['Artista'],\n",
108
+ " 'Musica': row['Musica'],\n",
109
+ " 'Pais': row['Pais'],\n",
110
+ " 'Genero': row['Genero'],\n",
111
+ " 'Duracao': row['Duracao'],\n",
112
+ " 'Ano_Musica': row['Ano_Musica'],\n",
113
+ " 'Decada_Musica': row['Decada_Musica'],\n",
114
+ " \n",
115
+ " # Features históricas\n",
116
+ " 'num_aparicoes': len(historico),\n",
117
+ " 'melhor_posicao': historico['Posicao'].min() if len(historico) > 0 else np.nan,\n",
118
+ " 'pior_posicao': historico['Posicao'].max() if len(historico) > 0 else np.nan,\n",
119
+ " 'posicao_media': historico['Posicao'].mean() if len(historico) > 0 else np.nan,\n",
120
+ " 'posicao_std': historico['Posicao'].std() if len(historico) > 0 else np.nan,\n",
121
+ " 'anos_desde_primeira': ano_atual - historico['Ano'].min() if len(historico) > 0 else 0,\n",
122
+ " 'apareceu_ano_anterior': 1 if (ano_atual - 1) in historico['Ano'].values else 0,\n",
123
+ " 'posicao_ano_anterior': historico[historico['Ano'] == ano_atual - 1]['Posicao'].values[0] \n",
124
+ " if (ano_atual - 1) in historico['Ano'].values else np.nan,\n",
125
+ " 'tendencia_posicao': None, # Calcular depois\n",
126
+ " 'idade_musica': ano_atual - row['Ano_Musica']\n",
127
+ " }\n",
128
+ " \n",
129
+ " # Calcular tendência (melhoria ou piora nas últimas aparições)\n",
130
+ " if len(historico) >= 2:\n",
131
+ " ultimas_posicoes = historico.tail(3)['Posicao'].values\n",
132
+ " if len(ultimas_posicoes) >= 2:\n",
133
+ " features['tendencia_posicao'] = ultimas_posicoes[-1] - ultimas_posicoes[0]\n",
134
+ " \n",
135
+ " features_list.append(features)\n",
136
+ " \n",
137
+ "df_features = pd.DataFrame(features_list)\n",
138
+ "print(f\"Features criadas com sucesso! Shape: {df_features.shape}\")\n",
139
+ "print(f\"\\nColunas disponíveis:\")\n",
140
+ "print(df_features.columns.tolist())"
141
+ ]
142
+ },
143
+ {
144
+ "cell_type": "markdown",
145
+ "id": "7cfd2b98",
146
+ "metadata": {},
147
+ "source": [
148
+ "## 4. Engenharia de features de aparição"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "code",
153
+ "execution_count": null,
154
+ "id": "97c3ae18",
155
+ "metadata": {},
156
+ "outputs": [],
157
+ "source": [
158
+ "print(\"Criando features de aparição...\")\n",
159
+ "\n",
160
+ "ano_limite = ano_predicao - 1\n",
161
+ "df_historico = df_features[df_features['Ano'] <= ano_limite].copy()\n",
162
+ "\n",
163
+ "# Obter todas as músicas únicas que já apareceram\n",
164
+ "musicas_unicas = df_historico['ID_Musica'].unique()\n",
165
+ "\n",
166
+ "features_atualizadas = []\n",
167
+ "\n",
168
+ "for musica_id in musicas_unicas:\n",
169
+ " df_musica = df_historico[df_historico['ID_Musica'] == musica_id].sort_values('Ano')\n",
170
+ " \n",
171
+ " # Pegar informações da última aparição\n",
172
+ " ultima_aparicao = df_musica.iloc[-1]\n",
173
+ " \n",
174
+ " # Calcular features baseadas em TODO o histórico\n",
175
+ " features = {\n",
176
+ " 'ID_Musica': musica_id,\n",
177
+ " 'Artista': ultima_aparicao['Artista'],\n",
178
+ " 'Musica': ultima_aparicao['Musica'],\n",
179
+ " 'Musica': ultima_aparicao['Musica'],\n",
180
+ " 'Pais': ultima_aparicao['Pais'],\n",
181
+ " 'Genero': ultima_aparicao['Genero'],\n",
182
+ " 'Duracao': ultima_aparicao['Duracao'],\n",
183
+ " 'Ano_Musica': ultima_aparicao['Ano_Musica'],\n",
184
+ " 'Decada_Musica': ultima_aparicao['Decada_Musica'],\n",
185
+ " \n",
186
+ " # FEATURES HISTÓRICAS (considerando TUDO até ano_limite)\n",
187
+ " 'num_aparicoes': len(df_musica),\n",
188
+ " 'melhor_posicao': df_musica['Posicao'].min(),\n",
189
+ " 'pior_posicao': df_musica['Posicao'].max(),\n",
190
+ " 'posicao_media': df_musica['Posicao'].mean(),\n",
191
+ " 'posicao_std': df_musica['Posicao'].std() if len(df_musica) > 1 else 0,\n",
192
+ " 'anos_desde_primeira': ano_limite - df_musica['Ano'].min(),\n",
193
+ " 'apareceu_ano_anterior': 1 if ano_limite in df_musica['Ano'].values else 0,\n",
194
+ " 'posicao_ano_anterior': df_musica[df_musica['Ano'] == ano_limite]['Posicao'].values[0] \n",
195
+ " if ano_limite in df_musica['Ano'].values else np.nan,\n",
196
+ " 'Posicao': ultima_aparicao['Posicao'], # Última posição conhecida\n",
197
+ " 'idade_musica': ano_limite - ultima_aparicao['Ano_Musica'],\n",
198
+ " 'anos_desde_ultima': ano_limite - df_musica['Ano'].max(),\n",
199
+ " \n",
200
+ " # Tendência das últimas 3 aparições\n",
201
+ " 'tendencia_posicao': 0\n",
202
+ " }\n",
203
+ " \n",
204
+ " # Calcular tendência\n",
205
+ " if len(df_musica) >= 2:\n",
206
+ " ultimas_posicoes = df_musica.tail(3)['Posicao'].values\n",
207
+ " if len(ultimas_posicoes) >= 2:\n",
208
+ " features['tendencia_posicao'] = ultimas_posicoes[-1] - ultimas_posicoes[0]\n",
209
+ " \n",
210
+ " # Calcular frequência de aparição (% anos que apareceu)\n",
211
+ " anos_possiveis = ano_limite - df_musica['Ano'].min() + 1\n",
212
+ " features['frequencia_aparicao'] = len(df_musica) / anos_possiveis if anos_possiveis > 0 else 0\n",
213
+ " \n",
214
+ " # Verificar se está em \"streak\" (apareceu nos últimos N anos consecutivos)\n",
215
+ " streak = 0\n",
216
+ " for ano_check in range(ano_limite, ano_limite - 5, -1):\n",
217
+ " if ano_check in df_musica['Ano'].values:\n",
218
+ " streak += 1\n",
219
+ " else:\n",
220
+ " break\n",
221
+ " features['streak_anos'] = streak\n",
222
+ " \n",
223
+ " # ===== NOVAS FEATURES PARA TRATAR OUTLIERS =====\n",
224
+ " \n",
225
+ " # Flag: música apareceu apenas 1 vez?\n",
226
+ " features['aparicao_unica'] = 1 if len(df_musica) == 1 else 0\n",
227
+ " \n",
228
+ " # Se apareceu apenas 1 vez, há quantos anos foi?\n",
229
+ " features['anos_desde_unica_aparicao'] = ano_limite - df_musica['Ano'].max() if len(df_musica) == 1 else 0\n",
230
+ " \n",
231
+ " # Taxa de \"dropout\" - chance de nunca mais voltar após primeira aparição\n",
232
+ " # Músicas que aparecem 1x e nunca voltam têm alta taxa de dropout\n",
233
+ " if len(df_musica) == 1:\n",
234
+ " anos_decorridos = ano_limite - df_musica['Ano'].max()\n",
235
+ " # Se passou 1 ano: dropout_score = 1, 2 anos = 2, etc\n",
236
+ " features['dropout_score'] = anos_decorridos\n",
237
+ " else:\n",
238
+ " features['dropout_score'] = 0\n",
239
+ " \n",
240
+ " # Consistência: razão entre aparições e anos possíveis\n",
241
+ " # Valores baixos indicam aparições esporádicas\n",
242
+ " features['consistencia'] = features['frequencia_aparicao']\n",
243
+ " \n",
244
+ " # Volatilidade de posição (amplitude)\n",
245
+ " if len(df_musica) > 1:\n",
246
+ " features['volatilidade_posicao'] = features['pior_posicao'] - features['melhor_posicao']\n",
247
+ " else:\n",
248
+ " features['volatilidade_posicao'] = 0\n",
249
+ " \n",
250
+ " # Força da música: combinação de múltiplos fatores\n",
251
+ " # Quanto maior, mais \"estabelecida\" é a música\n",
252
+ " forca = 0\n",
253
+ " if len(df_musica) >= 3:\n",
254
+ " forca += 3 # Apareceu múltiplas vezes\n",
255
+ " if features['frequencia_aparicao'] > 0.5:\n",
256
+ " forca += 2 # Alta frequência\n",
257
+ " if features['streak_anos'] >= 2:\n",
258
+ " forca += 2 # Em streak\n",
259
+ " if features['melhor_posicao'] <= 100:\n",
260
+ " forca += 1 # Já esteve no top 100\n",
261
+ " \n",
262
+ " features['forca_musica'] = forca\n",
263
+ " \n",
264
+ " # Penalidade para \"one-hit wonders\" (músicas que aparecem 1x e param)\n",
265
+ " # Quanto maior o tempo desde a única aparição, maior a penalidade\n",
266
+ " if features['aparicao_unica'] == 1:\n",
267
+ " features['penalidade_one_hit'] = min(5, features['anos_desde_unica_aparicao'])\n",
268
+ " else:\n",
269
+ " features['penalidade_one_hit'] = 0\n",
270
+ " \n",
271
+ " features_atualizadas.append(features)\n",
272
+ "\n",
273
+ "features_atualizadas = pd.DataFrame(features_atualizadas)\n",
274
+ "\n",
275
+ "print(f\"Features criadas com sucesso! Shape: {features_atualizadas.shape}\")\n",
276
+ "print(f\"\\nColunas disponíveis:\")\n",
277
+ "print(features_atualizadas.columns.tolist())"
278
+ ]
279
+ },
280
+ {
281
+ "cell_type": "markdown",
282
+ "id": "afc71e92",
283
+ "metadata": {},
284
+ "source": [
285
+ "## 5. Probabilidade de aparição a cada ano"
286
+ ]
287
+ },
288
+ {
289
+ "cell_type": "code",
290
+ "execution_count": null,
291
+ "id": "1d62500b",
292
+ "metadata": {},
293
+ "outputs": [],
294
+ "source": [
295
+ "print(\"Calculando probabilidades de aparição...\")\n",
296
+ "\n",
297
+ "# Preparar dados para cada música em cada ano\n",
298
+ "resultados = []\n",
299
+ "\n",
300
+ "for musica_id in df_features['ID_Musica'].unique():\n",
301
+ " df_musica = df_features[df_features['ID_Musica'] == musica_id].sort_values('Ano')\n",
302
+ " \n",
303
+ " for i in range(len(df_musica) - 1):\n",
304
+ " ano_atual = df_musica.iloc[i]['Ano']\n",
305
+ " ano_seguinte = ano_atual + 1\n",
306
+ " apareceu_seguinte = 1 if ano_seguinte in df_musica['Ano'].values else 0\n",
307
+ " \n",
308
+ " resultado = df_musica.iloc[i].to_dict()\n",
309
+ " resultado['apareceu_proximo_ano'] = apareceu_seguinte\n",
310
+ " resultados.append(resultado)\n",
311
+ "\n",
312
+ "\n",
313
+ "df_prob = pd.DataFrame(resultados)\n",
314
+ "\n",
315
+ "# Estatísticas gerais\n",
316
+ "taxa_aparicao_geral = df_prob['apareceu_proximo_ano'].mean()\n",
317
+ "print(f\"\\nTaxa geral de aparição no próximo ano: {taxa_aparicao_geral:.2%}\")\n",
318
+ "\n",
319
+ "# Análise por número de aparições\n",
320
+ "print(\"\\nTaxa de aparição por histórico:\")\n",
321
+ "for n in range(1, 6):\n",
322
+ " mask = df_prob['num_aparicoes'] == n\n",
323
+ " if mask.sum() > 0:\n",
324
+ " taxa = df_prob[mask]['apareceu_proximo_ano'].mean()\n",
325
+ " print(f\" {n} aparições anteriores: {taxa:.2%} (n={mask.sum()})\")"
326
+ ]
327
+ },
328
+ {
329
+ "cell_type": "markdown",
330
+ "id": "cdab325b",
331
+ "metadata": {},
332
+ "source": [
333
+ "## 6. Modelo para cálculo de probabilidade de aparição"
334
+ ]
335
+ },
336
+ {
337
+ "cell_type": "code",
338
+ "execution_count": null,
339
+ "id": "c96cd20b",
340
+ "metadata": {},
341
+ "outputs": [],
342
+ "source": [
343
+ "# Selecionar features numéricas\n",
344
+ "feature_cols_aparicao = ['num_aparicoes', 'melhor_posicao', 'pior_posicao', \n",
345
+ " 'posicao_media', 'anos_desde_primeira', 'apareceu_ano_anterior',\n",
346
+ " 'Duracao', 'idade_musica', 'Posicao']\n",
347
+ "\n",
348
+ "# Preparar dados\n",
349
+ "df_train = df_prob[df_prob['Ano'] < ano_predicao - 2].copy()\n",
350
+ "df_test = df_prob[df_prob['Ano'] >= ano_predicao - 2].copy()\n",
351
+ "\n",
352
+ "# Remover NaNs\n",
353
+ "df_train = df_train.dropna(subset=feature_cols_aparicao + ['apareceu_proximo_ano'])\n",
354
+ "df_test = df_test.dropna(subset=feature_cols_aparicao)\n",
355
+ "\n",
356
+ "X_train = df_train[feature_cols_aparicao]\n",
357
+ "y_train = df_train['apareceu_proximo_ano']\n",
358
+ "X_test = df_test[feature_cols_aparicao]\n",
359
+ "\n",
360
+ "# Treinar modelo\n",
361
+ "modelo_aparicao = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)\n",
362
+ "modelo_aparicao.fit(X_train, y_train)\n",
363
+ "\n",
364
+ "# Importância das features\n",
365
+ "feature_importance = pd.DataFrame({\n",
366
+ " 'feature': feature_cols_aparicao,\n",
367
+ " 'importance': modelo_aparicao.feature_importances_\n",
368
+ "}).sort_values('importance', ascending=False)\n",
369
+ "\n",
370
+ "print(\"\\nImportância das features para aparição:\")\n",
371
+ "print(feature_importance.to_string(index=False))"
372
+ ]
373
+ },
374
+ {
375
+ "cell_type": "markdown",
376
+ "id": "37aa65f2",
377
+ "metadata": {},
378
+ "source": [
379
+ "## 7. Geração de probabilidades de aparição"
380
+ ]
381
+ },
382
+ {
383
+ "cell_type": "code",
384
+ "execution_count": null,
385
+ "id": "45c8b7aa",
386
+ "metadata": {},
387
+ "outputs": [],
388
+ "source": [
389
+ "print(f\"{'='*80}\")\n",
390
+ "print(f\"CALCULANDO PREDIÇÕES PARA {ano_predicao}\")\n",
391
+ "print(f\"Considerando TODO o histórico até {ano_predicao - 1}\")\n",
392
+ "print(f\"{'='*80}\")\n",
393
+ "\n",
394
+ "# Recalcular features baseado em todo histórico até o ano anterior\n",
395
+ "df_atual = features_atualizadas\n",
396
+ "\n",
397
+ "print(f\"\\nTotal de músicas no histórico: {len(df_atual)}\")\n",
398
+ "print(f\"Músicas que apareceram em {ano_predicao - 1}: {df_atual['apareceu_ano_anterior'].sum()}\")\n",
399
+ "print(f\"Músicas com apenas 1 aparição: {df_atual['aparicao_unica'].sum()}\")\n",
400
+ "\n",
401
+ "predicoes = []\n",
402
+ "\n",
403
+ "for _, row in df_atual.iterrows():\n",
404
+ " try:\n",
405
+ " # Preparar features para aparição (incluindo as novas)\n",
406
+ " features_disponiveis = [f for f in feature_cols_aparicao if f in row.index]\n",
407
+ " X_aparicao = row[features_disponiveis].values.reshape(1, -1)\n",
408
+ " X_aparicao = np.nan_to_num(X_aparicao, nan=0)\n",
409
+ " \n",
410
+ " # Prever probabilidade de aparição\n",
411
+ " prob_aparicao_raw = modelo_aparicao.predict_proba(X_aparicao)[0][1]\n",
412
+ " \n",
413
+ " # ===== APLICAR AJUSTES PARA OUTLIERS =====\n",
414
+ " prob_aparicao = prob_aparicao_raw\n",
415
+ " \n",
416
+ " # REGRA 1: Penalizar músicas com apenas 1 aparição\n",
417
+ " if row['aparicao_unica'] == 1:\n",
418
+ " anos_desde = row['anos_desde_unica_aparicao']\n",
419
+ " \n",
420
+ " # Penalidade crescente: 1 ano = 20%, 2 anos = 40%, 3+ anos = 60%\n",
421
+ " if anos_desde == 0: # Apareceu no ano anterior\n",
422
+ " penalidade = 0.20\n",
423
+ " elif anos_desde == 1:\n",
424
+ " penalidade = 0.40\n",
425
+ " elif anos_desde == 2:\n",
426
+ " penalidade = 0.55\n",
427
+ " else:\n",
428
+ " penalidade = 0.70\n",
429
+ " \n",
430
+ " prob_aparicao *= (1 - penalidade)\n",
431
+ " \n",
432
+ " # REGRA 2: Bonus para músicas consistentes (múltiplas aparições)\n",
433
+ " if row['num_aparicoes'] >= 3 and row['frequencia_aparicao'] > 0.5:\n",
434
+ " bonus = min(0.15, row['frequencia_aparicao'] * 0.2)\n",
435
+ " prob_aparicao = min(0.99, prob_aparicao * (1 + bonus))\n",
436
+ " \n",
437
+ " # REGRA 3: Penalizar músicas que não aparecem há muito tempo\n",
438
+ " if row['anos_desde_ultima'] >= 3:\n",
439
+ " penalidade_ausencia = min(0.50, row['anos_desde_ultima'] * 0.10)\n",
440
+ " prob_aparicao *= (1 - penalidade_ausencia)\n",
441
+ " \n",
442
+ " # REGRA 4: Bonus para streak (apareceu nos últimos anos consecutivos)\n",
443
+ " if row['streak_anos'] >= 2:\n",
444
+ " bonus_streak = min(0.20, row['streak_anos'] * 0.05)\n",
445
+ " prob_aparicao = min(0.99, prob_aparicao * (1 + bonus_streak))\n",
446
+ " \n",
447
+ " # REGRA 5: Ajuste baseado na força geral da música\n",
448
+ " if row['forca_musica'] >= 5:\n",
449
+ " prob_aparicao = min(0.99, prob_aparicao * 1.10)\n",
450
+ " elif row['forca_musica'] == 0 and row['aparicao_unica'] == 1:\n",
451
+ " prob_aparicao *= 0.5 # Penalidade severa para one-hits fracos\n",
452
+ " \n",
453
+ " predicao = {\n",
454
+ " 'ID_Musica': row['ID_Musica'],\n",
455
+ " 'Artista': row['Artista'],\n",
456
+ " 'Musica': row['Musica'],\n",
457
+ " 'Genero': row['Genero'],\n",
458
+ " 'Pais': row['Pais'],\n",
459
+ " 'prob_aparicao_raw': prob_aparicao_raw, # Probabilidade antes dos ajustes\n",
460
+ " 'prob_aparicao': prob_aparicao, # Probabilidade ajustada\n",
461
+ " 'num_aparicoes_historicas': row['num_aparicoes'],\n",
462
+ " 'apareceu_ano_anterior': row['apareceu_ano_anterior'],\n",
463
+ " 'frequencia_aparicao': row['frequencia_aparicao'],\n",
464
+ " 'streak_anos': row['streak_anos'],\n",
465
+ " 'anos_desde_primeira': row['anos_desde_primeira'],\n",
466
+ " 'aparicao_unica': row['aparicao_unica'],\n",
467
+ " 'forca_musica': row['forca_musica'],\n",
468
+ " 'anos_desde_ultima': row['anos_desde_ultima']\n",
469
+ " }\n",
470
+ " \n",
471
+ " predicoes.append(predicao)\n",
472
+ " \n",
473
+ " except Exception as e:\n",
474
+ " print(f\"Erro ao processar música {row['ID_Musica']}: {e}\")\n",
475
+ " continue\n",
476
+ "\n",
477
+ "df_predicoes = pd.DataFrame(predicoes)\n",
478
+ "df_predicoes = df_predicoes.sort_values('prob_aparicao', ascending=False)\n",
479
+ "\n",
480
+ "\n",
481
+ "print(f\"\\nTotal de músicas analisadas: {len(df_predicoes)}\")\n",
482
+ "print(f\"Músicas com probabilidade > 50%: {(df_predicoes['prob_aparicao'] > 0.5).sum()}\")\n",
483
+ "print(f\"Músicas com probabilidade > 70%: {(df_predicoes['prob_aparicao'] > 0.7).sum()}\")\n",
484
+ "print(f\"Músicas com probabilidade > 90%: {(df_predicoes['prob_aparicao'] > 0.9).sum()}\")\n",
485
+ "\n",
486
+ "# Análise de one-hit wonders\n",
487
+ "one_hits = df_predicoes[df_predicoes['aparicao_unica'] == 1]\n",
488
+ "print(f\"\\n{'='*80}\")\n",
489
+ "print(f\"ANÁLISE DE ONE-HIT WONDERS (músicas com apenas 1 aparição)\")\n",
490
+ "print(f\"{'='*80}\")\n",
491
+ "print(f\"Total de one-hits: {len(one_hits)}\")\n",
492
+ "print(f\"Probabilidade média (one-hits): {one_hits['prob_aparicao'].mean():.2%}\")\n",
493
+ "print(f\"Probabilidade média (múltiplas aparições): {df_predicoes[df_predicoes['aparicao_unica'] == 0]['prob_aparicao'].mean():.2%}\")\n",
494
+ "print(f\"\\nOne-hits com maior probabilidade (top 10):\")\n",
495
+ "print(one_hits.head(10)[['Artista', 'prob_aparicao_raw', 'prob_aparicao', \n",
496
+ " 'anos_desde_ultima', 'forca_musica']].to_string(index=False))\n",
497
+ "\n",
498
+ "print(f\"\\n{'='*80}\")\n",
499
+ "print(f\"Top 20 músicas com MAIOR probabilidade de aparecer em {ano_predicao}:\")\n",
500
+ "print(f\"{'='*80}\")\n",
501
+ "print(df_predicoes.head(20)[['Artista', 'Genero', 'prob_aparicao', \n",
502
+ " 'num_aparicoes_historicas', 'streak_anos', \n",
503
+ " 'apareceu_ano_anterior', 'aparicao_unica']].to_string(index=False))\n",
504
+ "\n",
505
+ "# Criar visualização comparativa\n",
506
+ "print(\"\\n{'='*80}\")\n",
507
+ "print(\"Comparando probabilidades RAW vs AJUSTADAS\")\n",
508
+ "print(\"{'='*80}\")\n",
509
+ "\n",
510
+ "# Comparar diferenças para one-hits\n",
511
+ "one_hits_sorted = one_hits.copy()\n",
512
+ "one_hits_sorted['diferenca'] = one_hits_sorted['prob_aparicao_raw'] - one_hits_sorted['prob_aparicao']\n",
513
+ "one_hits_sorted = one_hits_sorted.sort_values('diferenca', ascending=False)\n",
514
+ "\n",
515
+ "print(\"\\nOne-hits com MAIOR ajuste (penalidade):\")\n",
516
+ "print(one_hits_sorted.head(10)[['Artista', 'prob_aparicao_raw', 'prob_aparicao', \n",
517
+ " 'diferenca', 'anos_desde_ultima']].to_string(index=False))"
518
+ ]
519
+ },
520
+ {
521
+ "cell_type": "markdown",
522
+ "id": "886e7408",
523
+ "metadata": {},
524
+ "source": [
525
+ "## 8. Visualização"
526
+ ]
527
+ },
528
+ {
529
+ "cell_type": "code",
530
+ "execution_count": null,
531
+ "id": "6c8f167b",
532
+ "metadata": {},
533
+ "outputs": [],
534
+ "source": [
535
+ "# ============================================================================\n",
536
+ "# 8. VISUALIZAÇÕES\n",
537
+ "# ============================================================================\n",
538
+ "\n",
539
+ "fig, axes = plt.subplots(1, 2, figsize=(20, 5))\n",
540
+ "\n",
541
+ "# 1. Distribuição de probabilidades\n",
542
+ "axes[0].hist(df_predicoes['prob_aparicao'], bins=30, edgecolor='black')\n",
543
+ "axes[0].set_xlabel('Probabilidade de Aparição')\n",
544
+ "axes[0].set_ylabel('Número de Músicas')\n",
545
+ "axes[0].set_title('Distribuição de Probabilidades de Aparição')\n",
546
+ "axes[0].axvline(0.5, color='red', linestyle='--', label='Threshold')\n",
547
+ "axes[0].legend()\n",
548
+ "\n",
549
+ "# 2. Relação entre número de aparições e probabilidade\n",
550
+ "df_prob_agg = df_prob.groupby('num_aparicoes')['apareceu_proximo_ano'].mean()\n",
551
+ "axes[1].plot(df_prob_agg.index, df_prob_agg.values, marker='o')\n",
552
+ "axes[1].set_xlabel('Número de Aparições Anteriores')\n",
553
+ "axes[1].set_ylabel('Taxa de Aparição no Próximo Ano')\n",
554
+ "axes[1].set_title('Histórico vs Probabilidade de Aparição')\n",
555
+ "axes[1].grid(True, alpha=0.3)"
556
+ ]
557
+ },
558
+ {
559
+ "cell_type": "markdown",
560
+ "id": "fa37c886",
561
+ "metadata": {},
562
+ "source": [
563
+ "## 9. Exportação"
564
+ ]
565
+ },
566
+ {
567
+ "cell_type": "code",
568
+ "execution_count": null,
569
+ "id": "802d426a",
570
+ "metadata": {},
571
+ "outputs": [],
572
+ "source": [
573
+ "df_predicoes.to_csv(\"../data/prob_proximo_ano.csv\", index=False)"
574
+ ]
575
+ }
576
+ ],
577
+ "metadata": {
578
+ "kernelspec": {
579
+ "display_name": "Python 3",
580
+ "language": "python",
581
+ "name": "python3"
582
+ },
583
+ "language_info": {
584
+ "codemirror_mode": {
585
+ "name": "ipython",
586
+ "version": 3
587
+ },
588
+ "file_extension": ".py",
589
+ "mimetype": "text/x-python",
590
+ "name": "python",
591
+ "nbconvert_exporter": "python",
592
+ "pygments_lexer": "ipython3",
593
+ "version": "3.12.1"
594
+ }
595
+ },
596
+ "nbformat": 4,
597
+ "nbformat_minor": 5
598
+ }
notebooks/wikpedia_scrapping.ipynb ADDED
@@ -0,0 +1 @@
 
 
1
+ {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"id":"jU_xnJo3rdIm"},"outputs":[],"source":["!pip install requests\n","!pip install beautifulsoup4"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"hotCo9ljroa4"},"outputs":[],"source":["import pandas as pd\n","import requests\n","from bs4 import BeautifulSoup\n","\n","df = pd.read_csv(\"./data/500+.csv\")\n","df['Duracao'] = '0:00'\n","\n","lista = df.drop_duplicates(subset='Musica_Wiki').Musica_Wiki.to_list()\n","\n","for musica in lista:\n","\t\ttry:\n","\t\t\tresponse = requests.get(url=musica)\n","\t\t\tsoup = BeautifulSoup(response.content, 'html.parser')\n","\t\t\tduracao = soup.select('.duration')[0].get_text()\n","\t\t\tprint(musica, duracao)\n","\t\t\tdf.loc[df['Musica_Wiki'] == musica, 'Duracao'] = duracao\n","\t\texcept:\n","\t\t\tdf.loc[df['Musica_Wiki'] == musica, 'Duracao'] = 'erro'\n","\n","df.to_csv('file.csv')"]}],"metadata":{"colab":{"authorship_tag":"ABX9TyOPbFXvcVqRkpw+WntjKwA8","provenance":[]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"name":"python"}},"nbformat":4,"nbformat_minor":0}
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ locales-all
2
+ ffmpeg
requirements.txt CHANGED
@@ -1,3 +1,7 @@
1
- altair
2
- pandas
3
- streamlit
 
 
 
 
 
1
+ pandas<=1.5.3
2
+ numpy<=1.25.2
3
+ plotly<=6.0.1
4
+ ipython<=7.34.0
5
+ streamlit
6
+ git+https://github.com/hukuhuku-matsuo/streamlit-timeline.git@main
7
+ git+https://github.com/dexplo/bar_chart_race.git@master
resources/evolucao_musicas.png ADDED
resources/favicon.ico ADDED
resources/logo.png ADDED

Git LFS Details

  • SHA256: 50bbba7445b3926d1d3ecc538b9abee0bc7b8b6040eabab19f616f6f40e48e6b
  • Pointer size: 131 Bytes
  • Size of remote file: 448 kB
resources/mapa_calor_musicas.png ADDED

Git LFS Details

  • SHA256: 86c59760903ffe73467b9953602a9e774d55a5aee9665aadba487db79bd479e4
  • Pointer size: 131 Bytes
  • Size of remote file: 194 kB
resources/musicas_decadas.png ADDED
resources/style.css ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ html {
2
+ --black: #21252a;
3
+ --grey-1: #343A40;
4
+ --grey-2: #495057;
5
+ --grey-3: #868E96;
6
+ --grey-4: #ADB5BD;
7
+ --grey-5: #CED4DA;
8
+ --grey-6: #DEE2E6;
9
+ --grey-7: #E9ECEF;
10
+ --grey-8: #F1F3F5;
11
+ --grey-9: #F8F9FA;
12
+ --trans-black: rgba(33, 37, 42, .9);
13
+ --red: #e10600;
14
+ --gold: #ffda65;
15
+ --gold-dark: #a3862c;
16
+ --bronze: #c99355;
17
+ --bronze-dark: #80582c;
18
+ }
19
+
20
+ html {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ *, *:before, *:after {
25
+ box-sizing: inherit;
26
+ }
27
+
28
+ html, body {
29
+ width: 100%;
30
+ height: 100%;
31
+ }
32
+
33
+ body {
34
+ font-family: "Inter UI", system-ui;
35
+ color: var(--black);
36
+ }
37
+
38
+ .list {
39
+ width: 100%;
40
+ max-width: 600px;
41
+ margin: 3rem auto 3rem;
42
+ border-radius: 0.4rem;
43
+ box-shadow: 0px 12px 25px rgba(0, 0, 0, 0.1), 0px 5px 12px rgba(0, 0, 0, 0.07);
44
+ }
45
+ @media screen and (max-width: 800px) {
46
+ .list {
47
+ margin: 0 auto;
48
+ }
49
+ }
50
+ .list__table {
51
+ width: 100%;
52
+ border-spacing: 0;
53
+ }
54
+ .list__header {
55
+ padding: 3rem 2rem;
56
+ background: white;
57
+ border-top-left-radius: 0.4rem;
58
+ border-top-right-radius: 0.4rem;
59
+ }
60
+ .list__header h1, .list__header h5 {
61
+ margin: 0;
62
+ padding: 0;
63
+ }
64
+ .list__header h5 {
65
+ margin-bottom: 0.5rem;
66
+ text-transform: uppercase;
67
+ color: var(--red);
68
+ }
69
+ .list__value {
70
+ display: block;
71
+ font-size: 18px;
72
+ }
73
+ .list__label {
74
+ font-size: 11px;
75
+ opacity: 0.6;
76
+ }
77
+ .list__row {
78
+ background: var(--grey-7);
79
+ cursor: pointer;
80
+ transition: all 300ms ease;
81
+ }
82
+ .list__row:hover, .list__row:focus {
83
+ transform: scale(1.05);
84
+ box-shadow: 0px 15px 28px rgba(0, 0, 0, 0.1), 0px 5px 12px rgba(0, 0, 0, 0.08);
85
+ transition: all 300ms ease;
86
+ }
87
+ .list__row:not(:last-of-type) .list__cell {
88
+ box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.08);
89
+ }
90
+ .list__row:first-of-type {
91
+ color: var(--gold-dark);
92
+ background: var(--grey-9);
93
+ }
94
+ .list__row:first-of-type .list__cell:first-of-type {
95
+ background: var(--gold);
96
+ color: var(--gold-dark);
97
+ }
98
+ .list__row:nth-of-type(2) {
99
+ color: var(--grey-2);
100
+ background: var(--grey-9);
101
+ }
102
+ .list__row:nth-of-type(2) .list__cell:first-of-type {
103
+ background: var(--grey-4);
104
+ color: var(--grey-2);
105
+ }
106
+ .list__row:nth-of-type(3) {
107
+ color: var(--bronze-dark);
108
+ background: var(--grey-9);
109
+ }
110
+ .list__row:nth-of-type(3) .list__cell:first-of-type {
111
+ background: var(--bronze);
112
+ color: var(--bronze-dark);
113
+ }
114
+ .list__cell {
115
+ padding: 1rem;
116
+ }
117
+ .list__cell:first-of-type {
118
+ text-align: center;
119
+ padding: 1rem 0.2rem;
120
+ background: var(--grey-5);
121
+ }
122
+
123
+ .list__icon__red {
124
+ color:red !important;
125
+ }
126
+
127
+ .list__icon__green {
128
+ color:green !important;
129
+ }
130
+
131
+ .list__icon__grey {
132
+ color:grey !important;
133
+ }
134
+
135
+ @keyframes fade {
136
+ from {
137
+ opacity: 0;
138
+ }
139
+ to {
140
+ opacity: 1;
141
+ left: 0;
142
+ }
143
+ }