# Aula 6 - Análise e visualização de dados em Python - Biblioteca pandas (Parte 2)

A parte prática da série segue nessa nova aula, ainda com o pandas; avançaremos um pouco mais na biblioteca, vendo seu poder e possibilidades de análise de dados.

O que veremos nessa aula:

- Um pouco mais sobre agrupamento: `value_counts()`, `unique()`e `count()`;
- Referenciando e copiando objetos no pandas.

## Um pouco mais sobre agrupamento

Aqui, vamos explorar mais um pouco as possibilidades que se tem ao usar o pandas para fazer agrupamentos e fatiamentos. Esses conceitos já foram explicados na aula passada; caso não lembre, é só voltar um post e verificar o texto e as referências sobre essa parte.

Anteriormente, nós vimos uma função chamada `value_counts()`, para ver quantos registros de cada sexo temos no *dataset* que estamos utilizando. Há um outro recurso usando essa função que é interessante, envolvendo a proporção de cada variável numa determinada coluna. Para ver o resultado disso, apenas é necessário adicionar um outro argumento dentro de `value_counts()`, envolvendo um processo de normalização dos dados. A linha de código fica assim:

```python
df[['sex']].value_counts(normalize = True)
```

> **Que tal fazer você mesmo?**
>
> Nessa parte envolvendo a função `value_counts()`, vimos o recurso de proporção, que usa o recurso da normalização, que é bastante interessante. Com esse mesmo recurso, podemos também extrair a representatividade de cada variável em porcentagem. Como você poderia fazer isso?

### Funções `unique()` e `count()`

Uma função parecida com o `value_counts()`, que já vimos antes, é a função `count()`. A diferença entre elas é que a segunda, em termos básicos, permite avaliar uma contagem de uma variável por outra, usando o `groupby` em vez de usar apenas uma variável para contar.

Para entender melhor: fizemos na aula anterior um agrupamento por sexo (variável `sex`), avaliando quantas ocorrências de cada sexo há no *dataframe* que estamos utilizando. Agora, trabalharemos um pouco com as identificações de espécies do conjunto de dados. Para descobrir quais são as espécies registradas, podemos lançar mão da função `unique()`:

```python
df['species_id'].unique() #Quais são as espécies que aparecem no dataframe
```

A partir disso, se queremos observar quantos registros existe para cada espécie, lançamos mão da função `count()`

```python
df.groupby('species_id')['record_id'].count() #Número de amostras por cada espécie
```

Ainda, se queremos saber a amostragem de uma espécie apenas, podemos adicionar mais um argumento à linha de código. Neste caso, ao final da linha, escrevemos o nome da espécie em colchetes (`['SP']`):

```python
df.groupby('species_id')['record_id'].count()['DO']
```

> **Que tal fazer você mesmo?**
>
> Aproveite a linha de código acima para obter resultados de espécies diferentes!

## A diferença entre associar/referenciar e copiar objetos

### Copiando objetos

Em aulas passadas, vimos como é possível criar objetos em um ambiente Jupyter com o Python, e a importância dessa criação para analisar dados. Às vezes, por certa precaução, nos é recomendado não trabalhar em cima de apenas um objeto de dado *dataframe* no Python; isso porque corremos o risco de perder informações ao chamar alguma função sem querer. Então, para garantirmos que ficará tudo bem, podemos criar novos objetos a partir de um mesmo *dataframe* com o mesmo conteúdo; isso seria uma **cópia** do objeto. Obtemos essas cópias de objetos usando o método `copy()`.

```python
df_copia = df.copy()
```

Com essa linha de código, portanto, criamos um novo objeto, chamado `df_copia`; tal objeto possui um novo *dataframe*, que tem conteúdo igual ao objeto `df`, sendo possível assim usá-lo para diferentes propósitos.

> **Que tal fazer você mesmo?**: Repita essa operação em um notebook Jupyter/do Google Colab, com a mesma fonte de dados que estamos utilizando, criando o objeto `df_copia`, e compare os objetos. Você pode fazer isso usando a função `head()`.

### Referenciando objetos

Suponhamos que alguém alegue que achou uma solução "mais simples e mais rápida" para essa situação. Observe a linha abaixo:

```pytho
df_copia = df
```

Com ela, argumenta esse alguém, criamos o mesmo objeto, `df_copia`, usando o `df` que utilizamos como *dataframe* anteriormente; isso teria o mesmo efeito que usar a função `copy()`, ao fazermos o mesmo exercício de comparação anterior. Logo, em vez de usarmos o `copy()` para fazer uma nova cópia de `df`, apenas criamos um novo objeto usando esse mesmo *dataframe*, porque dá na mesma... certo?

Nada mais errado. E eu explico o porquê:

O que se está a fazer aqui é, basicamente, dar um outro nome de objeto a um mesmo *dataframe*. Esse é o processo, em Python, chamado de **associação** ou **referência**. Então, nesse caso, `df` pode ser chamado tanto por esse nome, quanto pelo nome `df_copia`. 

Para entender melhor, usando a mesma fonte: Imagine que estamos criando agora três objetos distintos, a saber:

```python
df2 = df.copy()
df3 = df
```

Observe que, enquanto o `df2` é criado usando a função `copy()`, o `df3` é criado fazendo uma referência à `df`. Em princípio, os dois objetos estariam associados com o mesmo *dataframe*, se seguirmos a ideia anterior. Ao verificarmos os dois nomes usando a função `head()`ou `info()`, percebemos que eles são iguais, certo? 

Até aqui, o pensamento está OK, mas façamos uma coisa diferente nessa situação. Usemos o `df2` para fazer uma inclusão de colunas nele (isso será visto em uma próxima aula com mais detalhes, não se preocupe; a ideia agora é apenas fazer uma mudança):

```python
df2['alternative_id'] = 0.0 #Incluir uma coluna no 'df2'
```

Feita essa mudança, verifiquemos de novo , usando a função `head()` ou `info()`. Se você fizer isso no Google Colab ou em um Jupyter Notebook, perceberá que `df2` e `df3`, em seu conteúdo, são agora diferentes um do outro. Isso porque o que fizemos com o `df2` foi, conforme visto anteriormente, criar um novo *dataframe* de fato utilizando o conteúdo de `df`, enquanto `df3` acaba por ser um outro nome para o `df`. **Pode parecer repetitivo, mas é importante que fique clara essa diferença entre as duas funções, para que, quando for analisar seus próprios dados, não ocorram erros que podem consumir um tempo que será importante**.

-----

Nessa aula de pandas, vimos algumas coisas que são essenciais ter em mente ao usar a biblioteca para analisar seus dados, além de alguns recursos interessantes. Nas referências abaixo, há uma riqueza de conteúdo para entender melhor sobre os `counts`, além de mais links para você entender melhor os conceitos de referência e cópia de objetos. Além disso, no último link há um eBook gratuito feito pelos usuários do Stack Overflow para você aprender mais sobre a biblioteca pandas.

Nas próximas aulas, continuaremos a falar sobre aspectos da biblioteca pandas para análise de dados, além de começar a fazer uns *plots* simples. O início da visualização de dados na série está perto de chegar, não perca!

Um grande abraço e até a próxima aula!

**Leia mais:**

- [**Documentação do Pandas sobre value_counts**](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.value_counts.html)
- [**8 Python Pandas Value_counts() tricks that make your work more efficient - Re/thought**](https://re-thought.com/pandas-value_counts/)
- [**Getting more value from the Pandas’ value_counts() - Towards Data Science**](https://towardsdatascience.com/getting-more-value-from-the-pandas-value-counts-aa17230907a6)
- [**Python Variable Scope (passing by reference or copy?) - Stack Overflow**](https://stackoverflow.com/questions/5234126/python-variable-scope-passing-by-reference-or-copy)
- [**References and Copies - Python Essential Reference - InformIT**](https://www.informit.com/articles/article.aspx?p=453682&seqNum=4)
- [**Understanding Reference and Copy in Python - Gitconnected**](https://levelup.gitconnected.com/understanding-reference-and-copy-in-python-c681341a0cd8)
- [**Python - RIP Tutorial**](https://riptutorial.com/python)
- [**Learning pandas eBook - RIP Tutorial**](https://riptutorial.com/ebook/pandas)


