<jupyter_start><jupyter_text>Biblioteca Pandas (Créditos ao Professor <NAME>)* Biblioteca open source, escrita sobre a NumPy, extremamente eficiente para limpeza e manipulação de grandes quantidades de dados. Trabalha os dados de forma muito semelhante ao MS Excel (linhas e colunas).* Oferece estruturas e funções para facilitar a análise de dados em Python. Originalmente suas funções eram concentradas para análise de séries temporais, porém foi evoluindo e hoje possui suporte para diversos tipos de dados.* Outra grande vantagem é que boa parte do código foi implementada em linguagem C para melhorar a performance. Assim como a biblioteca NumPy buscou-se a facilidade da linguagem Python com a performance similar à da linguagem C.>  http://pandas.pydata.org/ Sua instalação é bastante simples, feita através do PIP ou Conda:```bashpip install pandas      ou      conda install pandas``` Neste módulo serão abordados os principais objetos do Pandas:* Series* DataFrames* Agrupamentos (GroupBy)* Entrada e Saída de dados* Índices Multiníveis* Dados Faltantes* Concatenação e mesclagem<jupyter_code>import numpy as np
import pandas as pd<jupyter_output><empty_output><jupyter_text>Series Uma Series é como um array unidimensional, uma lista de valores que sempre tem um índice relacionado, que rotula cada elemento da lista formando uma estrutura semelhando a de um dicionário do Python.<jupyter_code>lista = [10, 20, 30]
array = np.array([10, 20, 30])
dic = {'A':10, 'B':20, 'C':30}
my_list = pd.Series(lista)
my_list
coord = pd.Series(lista, ['X', 'Y', 'Z'])
coord
pd.Series(['X', 'Y', 'Z'], lista)
pd.Series(array)
s = pd.Series(dic)
s
type(s)<jupyter_output><empty_output><jupyter_text>Elementos podem ser acessados através de seu índice:<jupyter_code>coord['Y']
my_list[0]<jupyter_output><empty_output><jupyter_text>_**NOTE** que tanto índices quanto valores podem ser de qualquer tipo do Python ou de qualquer biblioteca referenciada_  Fornece informações estatísticas básicas sobre a série (no caso de séries numéricas)<jupyter_code>array = np.random.randint(1, 100, 10)
series = pd.Series(array)
series
series.mean()
series.std()
series.min()
series.max()
series.value_counts()
series.describe()<jupyter_output><empty_output><jupyter_text>Operações com escalares<jupyter_code>pd.Series([10, 20, 30, 40, 50]) / 10
pd.Series([10, 20, 30, 40, 50]) * 10<jupyter_output><empty_output><jupyter_text>Operações baseadas em indices:<jupyter_code>female_by_uf = pd.Series([1142487, 1185868, 248416, 5509991], ['AL', 'AM', 'AP', 'BA'])
male_by_uf = pd.Series([1004033, 1134335, 239029, 5053946], ['AL', 'AM', 'AP', 'BA'])
total_by_uf = female_by_uf + male_by_uf
total_by_uf
female_by_uf = pd.Series([71850,   1142487, 1185868, 248416,  5509991         ], ['AC', 'AL', 'AM', 'AP', 'BA'])
male_by_uf = pd.Series(  [1004033, 1134335, 239029,  5053946, 2991782, 1301956], ['AL', 'AM', 'AP', 'BA', 'CE', 'ES'])
total_by_uf = female_by_uf + male_by_uf
total_by_uf<jupyter_output><empty_output><jupyter_text>DataFrame DataFrame é o principal objeto da biblioteca Pandas. É composto basicamente por conjuntos de Series organizados de forma bidimensional e indexados. Pode ser criado de várias formas diferentes:<jupyter_code>df = pd.DataFrame([[10, 20, 30], [40, 50 , 60], [70, 80, 90]])
df
np.random.seed(100)
df = pd.DataFrame(np.random.randn(3, 4))
df
df = pd.DataFrame(np.random.randn(5, 4), index=['A', 'B', 'C', 'D', 'E'])
df
df = pd.DataFrame(np.random.randn(5, 4), columns=['A', 'B', 'C', 'D'])
df
df = pd.DataFrame(np.random.randn(5, 4), 
                  index=['A', 'B', 'C', 'D', 'E'], 
                  columns=['W', 'X', 'Y', 'Z'])
df<jupyter_output><empty_output><jupyter_text>A indexação de um DataFrame é muito semelhante à de utilizadas em dicionários:**As colunas de um DataFrame são Series**<jupyter_code>df['W']
df[['W', 'Y']]<jupyter_output><empty_output><jupyter_text>É possível criar novas colunas apenas atribuindo uma Series a ela:<jupyter_code>df['X_Y'] = df['X'] + df['Y']
df
df['Another'] = [23, 456, 89, -34, 66]
df
df['W']
df.drop('X_Y', axis=1)
df<jupyter_output><empty_output><jupyter_text>Porém esta operação não afeta diretamente o DataFrame, para que isso ocorra é necessário utilizar o parâmetro *inplace*<jupyter_code>df.drop('X_Y', axis=1, inplace=True)
df
df = df.drop('Another', axis=1)
df<jupyter_output><empty_output><jupyter_text>Elementos podem ser localizados usando o método *loc* seguindo a notação de linha X coluna:<jupyter_code>df
df.loc['A', 'W']
df.loc['C', 'Y']<jupyter_output><empty_output><jupyter_text>Quando a coluna é omitida, o retorno serão todos os elementos da linha selecionada em formato Series:<jupyter_code>df.loc['A']
df.loc['C']<jupyter_output><empty_output><jupyter_text>A notação linha X coluna também pode ser utilizada:<jupyter_code>df.loc[['A', 'C'], ['Y', 'Z']]<jupyter_output><empty_output><jupyter_text>É possivel localizar elementos através de índices numéricos assim como na NumPy através do método *iloc*:<jupyter_code>df.iloc[1:4]
df.iloc[:2, 2:]<jupyter_output><empty_output><jupyter_text>De uma forma muito semelhante à seleção condicional do NumPy, com os Dataframes e Series do Pandas é possível localizar elementos baseado em condições além da localização por linhas, colunas ou índices:<jupyter_code>df > 0
df[df > 0]<jupyter_output><empty_output><jupyter_text>Note que os indices dos elementos que não obedecem ao critério estabelecido, retornam com NaN (Não disponível)  A partir do DataFrame resultante, é possível fazer operações de *slicing*:<jupyter_code>df[df['W'] > 0]
df[df['W'] > 0][['X', 'Y']]

df[df['W'] > 0][['X', 'Y']][1:]<jupyter_output><empty_output><jupyter_text>É possível combinar condições em uma seleção condicional utilizando os operadores & e |<jupyter_code>mask1 = df['W'] > 0
df[mask1]
df[((df['X'] < 0) & (df['Y'] > 0) | (df['Z'] < 0))]<jupyter_output><empty_output><jupyter_text>É possível reiniciar as condições do índice de um DataFrame através do método *reset_index*:<jupyter_code>df.reset_index()<jupyter_output><empty_output><jupyter_text>Ou substituir o índice por valores de uma coluna do DataFrame qualquer:<jupyter_code>df
df['Estados'] = ['SP', 'RJ', 'MG', 'ES', 'SC']

df.set_index('Estados')
df<jupyter_output><empty_output><jupyter_text>Operações de agrupamento podem ser efetuadas através do método *groupby*:<jupyter_code>data = {'Team' : ['Alfa', 'Beta', 'Alfa', 'Beta', 'Beta', 'Alfa'], 
        'Seller' : ['Bob', 'Sam', 'Charlie', 'Smith', 'Sam', 'Bob'],
        'Sale' : [120, 250, 180, 100, 95, 278]}

df = pd.DataFrame(data)
df
by_team = df.groupby(by='Team')
by_team
df.index<jupyter_output><empty_output><jupyter_text>Várias informações podem ser extraídas a partir de um *DataFrameGroupBy*<jupyter_code>by_team.sum()
by_team.max()
by_team.min()
by_team.mean()
by_team.std()<jupyter_output><empty_output><jupyter_text>Algumas informações estatísticas podem ser obtidas através do método *describe*<jupyter_code>by_seller = df.groupby(by='Seller')
by_seller
by_seller.describe()<jupyter_output><empty_output><jupyter_text>As operações de agrupamento, retornam DataFrames que podem ser fatiados:<jupyter_code>s = by_seller.sum()
s
s.loc['Bob']
s[s['Sale'] > 200]<jupyter_output><empty_output><jupyter_text>Pandas fornece uma série de métodos para entrada e saída de dados. Basicamente em uma operação é possível ler um conjunto de dados de uma determinada fonte de forma que sejam já disponibilizadas em um DataFrame. Para isso é necessário conhecer o tipo e as características da fonte de dados, por exemplo, um arquivo CSV, seu separador e encoding. Os métodos *read_\** e *to_\** fornecem o suporte necessário para tal tarefa:<jupyter_code>df = pd.read_csv('../../resources/datasets/heroes.csv')
df.head()
data = {'Team' : ['Alfa', 'Beta', 'Alfa', 'Beta', 'Beta', 'Alfa'], 
        'Seller' : ['Bob', 'Sam', 'Charlie', 'Smith', 'Sam', 'Bob'],
        'Sale' : [120, 250, 180, 100, 95, 278]}

df = pd.DataFrame(data)
df.to_csv('../../resources/datasets/sales.csv')
df.to_json('../../resources/datasets/sales.json')<jupyter_output><empty_output><jupyter_text>Índices Multiníveis<jupyter_code>groups = [('Grupo 1', 'Sub_1'), 
          ('Grupo 1', 'Sub_2'), 
          ('Grupo 1', 'Sub_3'),
          ('Grupo 2', 'Sub_1'), 
          ('Grupo 2', 'Sub_2'), 
          ('Grupo 2', 'Sub_3')]
groups
i = pd.MultiIndex.from_tuples(groups)
i
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.rand(6, 2), index=i, columns=['C1', 'C2'])
df
df.index.names = ['Grupo', 'SubGrupo']
df
g1 = df.loc['Grupo 1']
g1
g1['C1']
g1.loc['Sub_1']
df.xs(key='Sub_1', level=1)
df.xs(key='Grupo 1', level=0)
data = {'Team' : ['Alfa', 'Beta', 'Alfa', 'Beta', 'Beta', 'Alfa'], 
        'Seller' : ['Bob', 'Sam', 'Charlie', 'Smith', 'Sam', 'Bob'],
        'Sale' : [120, 250, 180, 100, 95, 278]}

df = pd.DataFrame(data)
df
df = df.groupby(by=['Team', 'Seller']).sum()
df
df.index
df.xs(key='Bob', level=1)
df.xs(key='Beta', level=0)<jupyter_output><empty_output><jupyter_text>Dados FaltantesEventualmente poderá ocorrer uma situação onde o conjunto de informações fornecido tenha alguns _gaps_, algumas informações que não foram forneceidas em meio a muitas outras fornecidas, e esses "buracos" podem atrapalhar a análise do _DataSet_ pois as operações estatísticas básicas acabam não funcionando de forma adequada.O Pandas oferece algumas funcionalidades para contornar esses problemas e a utilização desses métodos irá depender do comportamento esperado do tratamento desses dados: Método: dropna()Este método excluirá (_drop_), dependendo dos parâmetros de entrada, linhas ou colunas com dados faltantes.<jupyter_code>df = pd.DataFrame({'A':[1, 2, 3], 'B':[np.nan, 4, 5], 'C':[np.nan, 7, np.nan]})
df
df.dropna()
df.dropna(axis=1)
df.dropna(axis=1, thresh=2)<jupyter_output><empty_output><jupyter_text>Método: fillna()O método _fillna()_ irá preencher os dados faltantes com critérios passados nos argumentos.<jupyter_code>df.fillna(0)
df.fillna(df.mean())
df.fillna(df.min())
df.fillna(df.max())
df = pd.DataFrame([[np.nan, 2, np.nan, 0],
                   [3, 4, np.nan, 1],
                   [np.nan, np.nan, np.nan, 5],
                   [np.nan, 3, np.nan, 4]],
                  columns=list('ABCD'))
df
df.fillna(method='ffill')
df.fillna(value={'A': 0, 'B': 99, 'C': -1, 'D': 'A'})
df.fillna(value={'A': 0, 'B': 99, 'C': -1, 'D': 'A'}, limit=1)<jupyter_output><empty_output><jupyter_text>Método: replace()O método replace substitui um valor por outro passados como argumentos, podendo aceitar escalares, cadeias de caracteres, listas e expressões regulares.<jupyter_code>s = pd.Series([0, 1, 2, 3, 4])
s
s.replace(0, 5)
df = pd.DataFrame({'A': [0, 1, 2, 3, 4],
                   'B': [5, 6, 7, 8, 9],
                   'C': ['a', 'b', 'c', 'd', 'e']})
df
df.replace(0, 5)
df.replace([0, 1, 2, 3, 7], -1)
df.replace([0, 1, 2, 3], [4, 3, 2, 1])
x = df.replace([0, 1, 2, 3], np.nan)
x
x.replace(np.nan, -1)
df.replace([1, 2], method='bfill')
df.replace({0: 10, 1: 'A', 'c': 'Python'})
df.replace({'A': 0, 'B': 8}, 100)<jupyter_output><empty_output><jupyter_text>Concatenação, junção e mesclagemExistem 3 formas diferentes de combinar DataFramas do Pandas: _Merging_, _Joining_ e _Concatenating_. Em todas elas o objetivo é o mesmo, trazer informações de DataFrames dispersos para um único DataFrame, com o objetivo de facilitar a análise dos dados.https://pandas.pydata.org/pandas-docs/stable/merging.html Método: concat()<jupyter_code>df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                    'B': ['B0', 'B1', 'B2', 'B3'],
                    'C': ['C0', 'C1', 'C2', 'C3'],
                    'D': ['D0', 'D1', 'D2', 'D3']})

df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
                    'B': ['B4', 'B5', 'B6', 'B7'],
                    'C': ['C4', 'C5', 'C6', 'C7'],
                    'D': ['D4', 'D5', 'D6', 'D7']}) 

df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                    'B': ['B8', 'B9', 'B10', 'B11'],
                    'C': ['C8', 'C9', 'C10', 'C11'],
                    'D': ['D8', 'D9', 'D10', 'D11']})
df1
df2
df3
pd.concat([df1, df2, df3])
pd.concat([df1, df2, df3], ignore_index=True)
pd.concat([df1, df2, df3], axis=1)
df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                        'B': ['B8', 'B9', 'B10', 'B11'],
                        'C': ['C8', 'C9', 'C10', 'C11'],
                        'D': ['D8', 'D9', 'D10', 'D11'], 
                        'F': ['F1', 'F2', 'F3', 'D11']})
df3
pd.concat([df1, df2, df3], sort=False)
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'],
                    'B': ['B0', 'B1', 'B2', 'B3'],
                    'C': ['C0', 'C1', 'C2', 'C3'],
                    'D': ['D0', 'D1', 'D2', 'D3']},
                    index=[0, 1, 2, 3])

df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'],
                    'B': ['B4', 'B5', 'B6', 'B7'],
                    'C': ['C4', 'C5', 'C6', 'C7'],
                    'D': ['D4', 'D5', 'D6', 'D7']},
                     index=[4, 5, 6, 7])

df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'],
                    'B': ['B8', 'B9', 'B10', 'B11'],
                    'C': ['C8', 'C9', 'C10', 'C11'],
                    'D': ['D8', 'D9', 'D10', 'D11']},
                    index=[8, 9, 10, 11])
df1
df2
df3
pd.concat([df1, df2, df3], axis=1)<jupyter_output><empty_output><jupyter_text>Método: merge()<jupyter_code>left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
                       'A': ['A0', 'A1', 'A2', 'A3'],
                       'B': ['B0', 'B1', 'B2', 'B3']})

right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'],
                        'C': ['C0', 'C1', 'C2', 'C3'],
                        'D': ['D0', 'D1', 'D2', 'D3']})
left
right
pd.merge(left, right, on='key')
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
                     'key2': ['K0', 'K1', 'K0', 'K1'],
                        'A': ['A0', 'A1', 'A2', 'A3'],
                        'B': ['B0', 'B1', 'B2', 'B3']})

right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
                      'key2': ['K0', 'K0', 'K0', 'K0'],
                         'C': ['C0', 'C1', 'C2', 'C3'],
                         'D': ['D0', 'D1', 'D2', 'D3']})
left
right
pd.merge(left, right, on=['key1', 'key2'])
pd.merge(left, right, how='left', on=['key1', 'key2'])
pd.merge(left, right, how='right', on=['key1', 'key2'])
pd.merge(left, right, how='outer', on=['key1', 'key2'])<jupyter_output><empty_output><jupyter_text>Método: join()<jupyter_code>left = pd.DataFrame({'A': ['A0', 'A1', 'A2'],
                     'B': ['B0', 'B1', 'B2']},
                     index=['K0', 'K1', 'K2'])

right = pd.DataFrame({'C': ['C0', 'C2', 'C3'],
                      'D': ['D0', 'D2', 'D3']},
                     index=['K0', 'K2', 'K3'])
left
right
left.join(right)
left.join(right, how='outer')
left.join(right, how='left')
left.join(right, how='right')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1862751655407665, 'fraction_numerical': 0.0446124005083272, 'mean_word_length': 4.049645390070922, 'pattern_counts': {'":': 0, '<': 128, '<?xml version=': 0, '>': 135, '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': '31', 'dir': 'jupyter-structured-clean-dedup', 'id': '440159', 'license': 'MIT', 'n_tokens_mistral': 6374, 'n_tokens_neox': 5938, 'n_words': 1525, 'path': 'lectures/Lecture0-review/2.Biblioteca Pandas.ipynb', 'repo_name': 'matheusmota/facens-dataviz', 'size': '14958'}