<jupyter_start><jupyter_text>Manipulação de dados - I  A biblioteca _pandas_- _pandas_ é uma biblioteca para leitura, tratamento e manipulação de dados em *Python* - Funções similares as de softwares de planilhamento (ex. _Microsoft Excel_, _LibreOffice Calc_, _Apple Numbers_) - Novas estruturas de dados que *pandas* introduz: *Series* e *DataFrames*- Para saber mais, veja a [página oficial](https://pandas.pydata.org/about/index.html) da biblioteca  Visão geral - Um *DataFame* possui uma estrutura tabular- Suas colunas são vetores unidimensionais (*Series*) - Suas linhas são rotuladas por um *index* - Usaremos o *numpy* integrado com *pandas*<jupyter_code>import numpy as np
import pandas as pd<jupyter_output><empty_output><jupyter_text>*Series*As *Series*:  * são vetores, ou seja, são *arrays* unidimensionais;  * possuem um *index* para cada entrada (e são muito eficientes para operar com base neles);  * podem conter qualquer um dos tipos de dados (`int`, `str`, `float` etc.).  Criando um objeto do tipo *Series*  - O método padrão é utilizar a função *Series*:```pythonserie_exemplo = pd.Series(dados_de_interesse, index=indice_de_interesse)```- `dados_de_interesse` pode ser:    * um dicionário (objeto do tipo `dict`);    * uma lista (objeto do tipo `list`);    * um objeto `array` do *numpy*;    * um escalar, tal como o número inteiro 1.  Criando *Series* a partir de dicionários<jupyter_code>dicionario_exemplo = {'Ana':20, 'João': 19, 'Maria': 21, 'Pedro': 22, 'Túlio': 20}
pd.Series(dicionario_exemplo)<jupyter_output><empty_output><jupyter_text>- *index* obtido a partir das "chaves" dos dicionários - ordem do *index* foi dada pela ordem de entrada no dicionário- podemos fornecer um novo *index* ao dicionário já criado<jupyter_code>pd.Series(dicionario_exemplo, index=['Maria', 'Maria', 'ana', 'Paula', 'Túlio', 'Pedro'])<jupyter_output><empty_output><jupyter_text>> Dados não encontrados são assinalados por um valor especial. O marcador padrão do *pandas* para dados faltantes é o `NaN` (*not a number*).  Criando *Series* a partir de listas<jupyter_code>lista_exemplo = [1,2,3,4,5] 
pd.Series(lista_exemplo)<jupyter_output><empty_output><jupyter_text>> Se os *index* não forem fornecidos, o *pandas* atribuirá automaticamente os valores `0, 1, ..., N-1`, onde `N` é o número de elementos da lista.  Criando *Series* a partir de *arrays* do *numpy*<jupyter_code>array_exemplo = np.array([1,2,3,4,5])
pd.Series(array_exemplo)<jupyter_output><empty_output><jupyter_text>Fornecendo um *index* na criação da *Series*O total de elementos do *index* deve ser igual ao tamanho do *array*. Caso contrário, um erro será retornado.<jupyter_code>pd.Series(array_exemplo, index=['a','b','c','d','e','f'])
pd.Series(array_exemplo, index=['a','b','c','d','e'])<jupyter_output><empty_output><jupyter_text>Além disso, não é necessário que que os elementos no *index* sejam únicos.<jupyter_code>pd.Series(array_exemplo, index=['a','a','b','b','c'])<jupyter_output><empty_output><jupyter_text>Um erro ocorrerá se uma operação que dependa da unicidade dos elementos no *index* for realizada, a exemplo do método `reindex`.<jupyter_code>series_exemplo = pd.Series(array_exemplo, index=['a','a','b','b','c']) 
series_exemplo.reindex(['b','a','c','d','e']) # 'a' e 'b' duplicados na origem<jupyter_output><empty_output><jupyter_text>Criando *Series* a partir de escalares<jupyter_code>pd.Series(1, index=['a', 'b', 'c', 'd'])<jupyter_output><empty_output><jupyter_text>Neste caso, um índice **deve** ser fornecido!  *Series* comportam-se como *arrays* do *numpy*- Uma *Series* do *pandas* comporta-se como um *array* unidimensional do *numpy*. - Pode ser utilizada como argumento para a maioria das funções do *numpy*. - A diferença é que o *index* aparece.<jupyter_code>series_exemplo = pd.Series(array_exemplo, index=['a','b','c','d','e'])
series_exemplo[2]
series_exemplo[:2]
np.log(series_exemplo)<jupyter_output><empty_output><jupyter_text>Mais exemplos:<jupyter_code>serie_1 = pd.Series([1,2,3,4,5]); serie_2 = pd.Series([4,5,6,7,8])
serie_1 + serie_2
serie_1 * 2 - serie_2 * 3<jupyter_output><empty_output><jupyter_text>Assim como *arrays* do *numpy*, as *Series* do *pandas* também possuem atributos *dtype* (data type).<jupyter_code>series_exemplo.dtype<jupyter_output><empty_output><jupyter_text>Se o interesse for utilizar os dados de uma *Series* do *pandas* como um *array* do *numpy*, basta utilizar o método `to_numpy` para convertê-la.<jupyter_code>series_exemplo.to_numpy()<jupyter_output><empty_output><jupyter_text>*Series* comportam-se como dicionáriosPodemos acessar os elementos de uma *Series* através das chaves fornecidas no *index*.<jupyter_code>series_exemplo
series_exemplo['a']<jupyter_output><empty_output><jupyter_text>Podemos adicionar novos elementos associados a chaves novas.<jupyter_code>series_exemplo['f'] = 6
series_exemplo
'f' in series_exemplo
'g' in series_exemplo<jupyter_output><empty_output><jupyter_text>Neste examplo, tentamos acessar uma chave inexistente. Logo, um erro ocorre.<jupyter_code>series_exemplo['g']
series_exemplo.get('g')<jupyter_output><empty_output><jupyter_text>Entretanto, podemos utilizar o método `get` para lidar com chaves que possivelmente inexistam e adicionar um `NaN` do *numpy* como valor alternativo se, de fato, não exista valor atribuído.<jupyter_code>series_exemplo.get('g',np.nan)<jupyter_output><empty_output><jupyter_text>O atributo `name`Uma *Series* do *pandas* possui um atributo opcional `name` que nos permite identificar o objeto. Ele é  bastante útil em operações envolvendo *DataFrames*.<jupyter_code>serie_com_nome = pd.Series(dicionario_exemplo, name = "Idade")
serie_com_nome<jupyter_output><empty_output><jupyter_text>A função `date_range`Em muitas situações, os índices podem ser organizados como datas. A função `data_range` cria índices a partir de datas. Alguns argumentos desta função são:- `start`: `str` contendo a data que serve como limite à esquerda das datas. Padrão: `None`- `end`: `str` contendo a data que serve como limite à direita das datas. Padrão: `None`- `freq`: frequência a ser considerada. Por exemplo, dias (`D`), horas (`H`), semanas (`W`), fins de meses (`M`), inícios de meses (`MS`), fins de anos (`Y`), inícios de anos (`YS`) etc. Pode-se também utilizar múltiplos (p.ex. `5H`, `2Y` etc.). Padrão: `None`. - `periods`: número de períodos a serem considerados (o período é determinado pelo argumento `freq`). Abaixo damos exemplos do uso de `date_range` com diferente formatos de data.<jupyter_code>pd.date_range(start='9/19/2021', freq='W', periods=10) 
pd.date_range(start='2010-05-10', freq='Y', periods=10)
pd.date_range('9/19/2021', freq='5H', periods=11)
pd.date_range(start='2024-02-29', freq='365D', periods=9)<jupyter_output><empty_output><jupyter_text>O exemplo a seguir cria duas *Series* com valores aleatórios associados a um interstício de 10 dias.<jupyter_code>indice_exemplo = pd.date_range('2020-01-01', periods=10, freq='D')
serie_1 = pd.Series(np.random.randn(10),index=indice_exemplo)   
serie_2 = pd.Series(np.random.randn(10),index=indice_exemplo)

serie_1, serie_2<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13168205989364679, 'fraction_numerical': 0.015253288553036664, 'mean_word_length': 6.985474860335196, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 83, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '416764', 'license': 'CC0-1.0', 'n_tokens_mistral': 2808, 'n_tokens_neox': 2697, 'n_words': 708, 'path': '_build/jupyter_execute/rise/10a-pandas-series-rise.ipynb', 'repo_name': 'gcpeixoto/ICD', 'size': '7146'}