Introdução à análise de dados com PySpark utilizando os dados dos campeões de League of Legends
Tags: pyspark, python, dataanalysis, dataengineering
Published: 2022-09-15 17:57:35

O [League of Legends](https://www.leagueoflegends.com/pt-br/), também conhecido como lolzinho, para os íntimos, é um jogo ambientado no mundo fantasioso de Runeterra, com batalhas sangrentas e muita magia. Em League of Legends, os jogadores controlam personagens conhecidos como campeões, cada um com suas habilidades e diferentes estilos de jogo.

Neste artigo, iremos analisar algumas estatísticas desses campeões fazendo o uso do [PySpark](https://spark.apache.org/docs/latest/api/python/index.html), uma API do framework Apache Spark desenvolvida para a linguagem de programação Python 🐍. Os dados serão extraídos da web API [Data Dragon](https://developer.riotgames.com/docs/lol#data-dragon), uma API pública da Riot Games.

Para isso, vamos desenvolver um notebook no [Google Colab](https://colab.research.google.com/), um serviço de nuvem gratuito criado pelo Google para incentivar pesquisas na área de machine learning e inteligência artificial.

Caso não saiba como usar o Google Colab, confira [este excelente artigo](https://www.alura.com.br/artigos/google-colab-o-que-e-e-como-usar) da Alura escrito pelo Thiago Santos que ensina, de forma muito didática, como usar o Colab e criar seus primeiros códigos!

O notebook deste artigo também está disponível em meu [GitHub](https://github.com/geazi-anc/lol-champions-analysis) 😉.

Peguem suas espadas, preparem suas magias, e vamos começar ⚔🧙🏼‍♀️!

## Instalação

Antes de começarmos, é necessário fazer a instalação de duas bibliotecas: [PySpark](https://spark.apache.org/docs/latest/api/python/index.html) e [Requests](https://requests.readthedocs.io/en/latest/).

A biblioteca PySpark, como foi dito, é a API oficial do Python para o Apache Spark. É com ela que vamos realizar nossa análise de dados 🎲.

Já a biblioteca Requests é uma biblioteca que nos permite fazer solicitações HTTP a um determinado website. Mediante a ela que iremos extrair os dados dos campeões através da API pública da Riot Games 🚀.

Crie uma nova célula de código no Colab e execute a seguinte linha:

```
!pip install pyspark
!pip install requests
```

## Inicialização

Logo após a instalação das bibliotecas, precisamos inicializar o Apache Spark. Para isso, importamos a classe **SparkSession** dentro do módulo **sql** da biblioteca **pyspark**.

Depois da importação, instanciamos a classe SparkSession através de uma série de métodos encadeados, como **appName** e **getOrCreate**.

```
from pyspark.sql import SparkSession

spark = (SparkSession.builder
         .appName("Introdução à análise de dados com PySpark utilizando os dados dos campeões de League of Legends")
         .getOrCreate()
         )
```

## Extração de dados dos campeões

A extração dos dados dos campeões de League of Legends é feita através de uma solicitação HTTP à um endpoint da API [Data Dragon](https://developer.riotgames.com/docs/lol#data-dragon), uma API pública da Riot Games que centraliza os dados do jogo, como campeões, itens, magias e ETC.

A resposta é um objeto JSON semelhante a este:

```
{
    "type": "champion",
    "format": "standAloneComplex",
    "version": "12.17.1",
    "data": {
        "Aatrox": {},
        "Ahri": {...},
        "Akali": {...},
        "Akshan": {...},
        "Alistar": {...},
        ...,
    }
}
```

Observe que os dados que queremos está dentro da chave **data**. Vamos pegar esses dados, descartando os demais, e exibir apenas o nome de todos os campeões.

Crie uma nova célula de código e execute o seguinte bloco:

```
import requests

response=requests.get(
"https://ddragon.leagueoflegends.com/cdn/12.17.1/data/pt_BR/champion.json")

champions=response.json().get("data")
champions.keys()
```

**Resultado**:

`dict_keys(['Aatrox', 'Ahri', 'Akali', 'Akshan', 'Alistar', ...])`

Também podemos ver os dados de um campeão em específico. Nesse caso, vamos ver os dados estatísticos da Akali.

`champions.get("akali")`

**Resultado**:

```
{'version': '12.17.1',
 'id': 'Akali',
 'key': '84',
 'name': 'Akali',
 'title': 'a Assassina Renegada',
 'blurb': 'Abandonando a Ordem Kinkou e seu título de Punho das Sombras, Akali agora ataca sozinha, pronta para ser a arma mortal que seu povo precisa. Embora ela mantenha tudo o que aprendeu com seu mestre Shen, ela se comprometeu a defender Ionia de seus...',
 'info': {'attack': 5, 'defense': 3, 'magic': 8, 'difficulty': 7},
 'image': {'full': 'Akali.png',
  'sprite': 'champion0.png',
  'group': 'champion',
  'x': 96,
  'y': 0,
  'w': 48,
  'h': 48},
 'tags': ['Assassin'],
 'partype': 'Energia',
 'stats': {'hp': 570,
  'hpperlevel': 119,
  'mp': 200,
  'mpperlevel': 0,
  'movespeed': 345,
  'armor': 23,
  'armorperlevel': 4.7,
  'spellblock': 37,
  'spellblockperlevel': 2.05,
  'attackrange': 125,
  'hpregen': 9,
  'hpregenperlevel': 0.9,
  'mpregen': 50,
  'mpregenperlevel': 0,
  'crit': 0,
  'critperlevel': 0,
  'attackdamage': 62,
  'attackdamageperlevel': 3.3,
  'attackspeedperlevel': 3.2,
  'attackspeed': 0.625}}
```

## Limpesa dos dados

Antes de começarmos de fato com a análise, é necessário fazermos uma limpesa prévia nos dados. Vamos pegar apenas os que nos interessa, e remover os dicionários dentro de dicionários, deixando um único dicionário para cada campeão com os dados necessários.

```
champions=[{'name': value['name'], 'title': value['title'], **value['info'], **value['stats']} for key, value in champions.items()]
champions[2]
```

**Resultado**:

```
{'name': 'Akali',
 'title': 'a Assassina Renegada',
 'attack': 5,
 'defense': 3,
 'magic': 8,
 'difficulty': 7,
 'hp': 570,
 'hpperlevel': 119,
 'mp': 200,
 'mpperlevel': 0,
 'movespeed': 345,
 'armor': 23,
 'armorperlevel': 4.7,
 'spellblock': 37,
 'spellblockperlevel': 2.05,
 'attackrange': 125,
 'hpregen': 9,
 'hpregenperlevel': 0.9,
 'mpregen': 50,
 'mpregenperlevel': 0,
 'crit': 0,
 'critperlevel': 0,
 'attackdamage': 62,
 'attackdamageperlevel': 3.3,
 'attackspeedperlevel': 3.2,
 'attackspeed': 0.625}
```

## Criando o DataFrame

Agora sim! Os dados dos campeões estão limpos, então já podemos criar nosso DataFrame com o Spark.

Infelizmente, o Spark é um tanto... seletivo com o tipo de objeto que passamos a ele para criar um DataFrame. Logo, nosso objeto atual champions, que é composto de uma lista de dicionários, não é aceito pelo Spark.

Mas existe uma solução👏🏼. A biblioteca Pandas é muito mais flexível no que se refere a criação de um novo DataFrame. Portanto, é possível criar um DataFrame do Pandas com nosso objeto champions atual, e em seguida criar um DataFrame do Spark com base no DataFrame criado pelo Pandas.

```
import pandas as pd

df = spark.createDataFrame(pd.DataFrame(champions))

df.select("name", "title").show(5, False)
```

**Resultado**:

```
+-------+-----------------------+
|name   |title                  |
+-------+-----------------------+
|Aatrox |a Espada Darkin        |
|Ahri   |a Raposa de Nove Caudas|
|Akali  |a Assassina Renegada   |
|Akshan |o Sentinela Rebelde    |
|Alistar|o Minotauro            |
+-------+-----------------------+
only showing top 5 rows
```

## Concatenação de colunas

Não sei vocês, mas acho um tanto incômodo ficar selecionando o nome e os títulos dos campeões cada vez que formos visualisar seus dados. Então, vamos concatenar as colunas **name** e **title** em uma nova coluna, chamada **full_name**.

Para isso, vamos primeiramente utilizar o método **withColumn**. Em resumo, esse método nos permite criar uma nova coluna em nosso DataFrame.

O primeiro parâmetro do método é o nome da nossa coluna. Já o segundo parâmetro são os dados que queremos popular nossa nova coluna. Nesse caso, a concatenação da coluna **name** com a coluna **title**.

Para concatenar as colunas de strings, vamos utilizar a função **concat**.
Esta função recebe como parâmetros o nome das colunas que queremos concatenar. Contudo, não podemos passar apenas o nome dessas colunas. Caso contrário o nome e os títulos ficariam colados um ao outro. Então também usamos a função **lit**, que cria uma nova coluna literal com o valor que passamos a ela, isto é: ", ".

```
from pyspark.sql import functions as F

df = df.withColumn("full_name", F.concat(df.name, F.lit(", "), df.title))
df.select("full_name").show(5, False)
```

**Resultado**:

```
+-----------------------------+
|full_name                    |
+-----------------------------+
|Aatrox, a Espada Darkin      |
|Ahri, a Raposa de Nove Caudas|
|Akali, a Assassina Renegada  |
|Akshan, o Sentinela Rebelde  |
|Alistar, o Minotauro         |
+-----------------------------+
only showing top 5 rows
```

## Quem são os campeões mais poderosos de League of Legends?

Curioso para saber quem são os campeões mais poderosos de League of Legends? Pois é, eu também estou. Vamos descobrir 👀!

Para esta análise, considere que o que determina o nível de poder de um campeão são seus valores de ataque, armadura, vida e mana.

Então, para vermos quem são os campeões mais poderosos, basta ordenarmos nosso DataFframe com base nessas colunas, de modo decrescente.

> Uma pequena observação: atualmente todos os campeões estão no nível um.

```
base_columns = ["attackdamage", "armor", "hp", "mp"]

(df.orderBy(*base_columns, ascending=False)
 .select("full_name", *base_columns)
 .show(5, False)
 )
```

**Resultado**:

```
+---------------------------------+------------+-----+-----+-----+
|full_name                        |attackdamage|armor|hp   |mp   |
+---------------------------------+------------+-----+-----+-----+
|Tryndamere, o Rei Bárbaro        |72.0        |33   |696.0|100.0|
|Cho'Gath, o Terror do Vazio      |69.0        |38   |644.0|270.0|
|Renekton, o Carniceiro das Areias|69.0        |35   |660.0|100.0|
|Ornn, O Fogo sob a Montanha      |69.0        |33   |660.0|340.6|
|Kayn, o Ceifador das Sombras     |68.0        |38   |655.0|410.0|
+---------------------------------+------------+-----+-----+-----+
only showing top 5 rows
```

## Level up!

Como dito, atualmente nossos campeões estão no nível 1. Vamos alterar o nível deles para o nível 10.

Observe que as estatísticas dos campeões devem acompanhar seus crescimentos conforme o passar dos níveis. Nesta análise, vamos alterar apenas os valores de dano, armadura, vida e mana.

Para alterarmos esses valores, vamos fazer o uso do método **withColumns**.
Este método recebe um objeto do tipo dicionário, onde as chaves são os nomes das colunas, e seus valores são as colunas com os dados alterados.

```
level = 10

df2 = df.withColumns({
    "attackdamage": df.attackdamage+df.attackdamageperlevel*level,
    "armor": df.armor+df.armorperlevel*level,
    "hp": df.hp+df.hpperlevel*level,
    "mp": df.mp+df.mpperlevel*level
})
```

## Quem são os campeões mais poderosos de League of Legends (de novo)?

Com todos os campeões já no nível 10, vamos ver se o rank de poder da análise anterior se manteve ou se houve mudança.
Lembrando que ainda estamos analisando o nível de poder apenas com base nas colunas dano, armadura, vida e mana.

```
(df2.orderBy(*base_columns, ascending=False)
 .select("full_name", *base_columns)
 .show(5, False)
 )
```

**Resultado**:

```
+-----------------------------+------------+-----+------+-----+
|full_name                    |attackdamage|armor|hp    |mp   |
+-----------------------------+------------+-----+------+-----+
|Illaoi, a Sacerdotisa Cráquem|118.0       |85.0 |1746.0|800.0|
|Olaf, o Berserker            |115.0       |77.0 |1835.0|816.0|
|Darius, a Mão de Noxus       |114.0       |91.0 |1792.0|838.0|
|Yorick, o Pastor de Almas    |112.0       |91.0 |1790.0|900.0|
|Cho'Gath, o Terror do Vazio  |111.0       |85.0 |1584.0|870.0|
+-----------------------------+------------+-----+------+-----+
only showing top 5 rows
```

## Estatísticas dos níveis de poderes

Para finalizar, vamos ver algumas estatísticas simples de todos os nossos campeões no nível 10.

Vamos determinar a média do dano, o máximo do hp e da mana, e o mínimo da armadura.

Utilizaremos o método **agg**. Este método recebe como parâmetro um dicionário, onde as chaves são o nome das colunas que queremos analisar e os valores são as funções que queremos aplicar sobre elas.

```
(df2.agg({
    "attackdamage": "mean",
    "hp": "max",
    "mp": "max",
    "armor": "min"
})
    .show()
)
```

**Resultado**:

```
+-------+----------+-----------------+-------+
|max(mp)|min(armor)|avg(attackdamage)|max(hp)|
+-------+----------+-----------------+-------+
|10000.0|      28.0|91.40481987577641| 1892.0|
+-------+----------+-----------------+-------+
```

## Considerações finais

É isso, meus amigos. Finalizamos nossa análise por aqui 🎆.

Neste artigo demonstrei como aplicar uma análise bem simples sobre os dados dos campeões de League of Legends. Fizemos a extração dos dados por meio da API públilca da Riot Games; fizemos uma limpesa prévia nos dados; criamos uma nova coluna com o resultado da concatenação dos nomes dos campeões e seus títulos; ranqueamos os campeões mais poderosos com base em seus níveis de poder; e, por fim, fizemos uma análise das estatísticas dos campeões tanto no nível 1 quanto no nível 10.

Espero que tenham gostado. Até a próxima 💚!