<jupyter_start><jupyter_text>LI PROGRAMA DE VERÃO (2022 - edição 51)   Recapitulando a aula anterior:  Fechando!  * Tipagem dinâmica* Sem delimitadores {};* Instalação de libs https://www.devmedia.com.br/meetup-introducao-ao-python/40245  Referencias * devmedia.com.br* https://jovemnerd.com.br/nerdcast/nerdtech/vingadores-do-data-science/* https://www.youtube.com/watch?v=5b9Z8toVaAU* https://www.devmedia.com.br/tipos-de-dados-em-python-string/40669* https://gvanrossum.github.io/  Bibliotecas* Pandas* Numpy* MatplotLib* Sklearn  Pandas https://pandas.pydata.org/docs/user_guide/index.html  Estruturas de dados:* Series: similar a uma lista * DataFrame: forma tabular de apresentar os dados. Onde cada coluna é uma série  SeriesÉ um objeto de uma dimensão, que contém um array de dados ( de qualquer tipo de dados do Numpy) e um array associado com os labels dos dados, chamado *index*.<jupyter_code>import pandas as pd
songs = pd.Series([145, 142, 38, 13], name='counts')
songs<jupyter_output><empty_output><jupyter_text>Observe que como não especificamos nenhum valor para os indices, são atribuídos indicies de 0 até N-1.  É possível recuperar a representação de um array ou do indice:<jupyter_code>songs.values
songs.index
letters = pd.Series(['a', 'b', 'c'], name='letters')
letters<jupyter_output><empty_output><jupyter_text>É possivel criar series com tipos mistos:<jupyter_code>mix = pd.Series(['a', 1, 'c'], name='letters')
mix<jupyter_output><empty_output><jupyter_text>**Nota:** apesar de permitido construir listas com tipos mistos, isso não é recomendado, pois atrapalha a performance das operações<jupyter_code>print(type(mix[0]))
print(type(mix[1]))
songs.index<jupyter_output><empty_output><jupyter_text>Frequentemente é desejado criar series com indices que identificam cada ponto, da seguinte forma:<jupyter_code>songs3 = pd.Series([145, 142, 38, 13], name='counts', index=['Paul', 'John', 'George', 'Ringo'])
songs3
songs3.index<jupyter_output><empty_output><jupyter_text>Também podemos criar um serie em que um dos valores é NaN:<jupyter_code>nan_ser = pd.Series([2, None], index=['Ono', 'Clapton'])  # observe que esta serie é do tipo float, isso pq apenas float suportam o NaN
nan_ser<jupyter_output><empty_output><jupyter_text>Mas se você contar a quantidade de itens da série, o NaN será ignorado:<jupyter_code>nan_ser.count() # observe que o nan é ignorado na maioria da operacoes<jupyter_output><empty_output><jupyter_text>Como visto no exemplo acima, em que verificamos o tipo da posição de um valor da série, é possivel indexar e então acessar um valor ou conjunto de valores:<jupyter_code># acesso a uma posicao 
songs[1]
songs3[['Paul']]
songs3[['Paul', 'George']]
mask = songs3 > songs3.median() # boolean array
mask<jupyter_output><empty_output><jupyter_text>Observe que operações Numpy array, tais como filtros, multiplicação por um escalar ou aplicação de funções matemáticas, preservam o valor do index:<jupyter_code># filtro 
songs3[mask]<jupyter_output><empty_output><jupyter_text>**Nota:** usar series, quando estamos lidando com dados númericos, ao invés de listas, é mais rápido, consome menos memória e já existem metodos que facilitam a mainpulação dos dados.  Vamos ver com mais detalhes:  CRUD  * Creation<jupyter_code># count of songs attributed to <NAME> during the 
# final years of The Beatles and the release of his 1970 album All Things Must Pass.
george_dupe = pd.Series([10, 7, 1, 22], index=['1968', '1969', '1970', '1970'], name='George Songs')
george_dupe<jupyter_output><empty_output><jupyter_text>**Nota:** Observe que o index são string não unicas Podemos criar uma serie a partir de um dicionário, uma vez que podemos pensar em uma série como um dicionário ordenado, de tamanho fixo, mapeando valores para indices:<jupyter_code>dicionario = {'1969': 7, '1970':1, '1970':2}
dicionario<jupyter_output><empty_output><jupyter_text>Note que, um dicionário não permite chaves duplicadas, então precisamos de uma lista (=<jupyter_code>dicionario_2 = {'1969': 7, '1970': [1, 22]}
dicionario_2
# criando uma serie a partir de um dicionario
g2 = pd.Series(dicionario_2, index=['1969', '1970', '1970'])
g2<jupyter_output><empty_output><jupyter_text>* Reading<jupyter_code>george_dupe['1968'] # retorna um valor 
george_dupe['1970'] # não retorna um unico valor
for item in george_dupe:
    print(item)
22 in george_dupe  # esse metodo nao itera sobre os valores 
'1970' in george_dupe # esse metodo itera sobre o index
set(george_dupe) # transforme em um conjunto
22 in set(george_dupe) # agora sim o metodo itera nos valores
22 in george_dupe.values # ou assim 
for item in george_dupe.iteritems():
    print(item)<jupyter_output><empty_output><jupyter_text>* Updating<jupyter_code>george_dupe['1969'] = 6
george_dupe['1969']
george_dupe['1973'] = 11
george_dupe
george_dupe
# o que vcs acham que vai acontecer aqui?
george_dupe['1970'] = 2
george_dupe
george_dupe.iloc[3] = 22
george_dupe
george_dupe.append(pd.Series({'1974':9})) # nesse caso eh criada outra lista, ou seja, nao alteramos a serie
george_dupe<jupyter_output><empty_output><jupyter_text>* Deletion<jupyter_code>del george_dupe['1973']
george_dupe
george_dupe[george_dupe <= 2]<jupyter_output><empty_output><jupyter_text>Indexing<jupyter_code>george = pd.Series([10, 7],index=['1968', '1969'], name='George Songs')
george
 george.index
dupe = pd.Series([10, 2, 7], index=['1968', '1968', '1969'], name='George Songs')
dupe.index.is_unique
george.index.is_unique
george
george[0]
george['1968']
george[-2]
dupe
dupe['1968']
george_i = pd.Series([10, 7], index=[1968, 1969], name='George Songs')
george_i
george_i[-1] # conseguem chutar porque deu erro aqui?<jupyter_output><empty_output><jupyter_text>* .iloc: posição* .loc : label<jupyter_code>george.iloc[0]
george.iloc[-1]
george.iloc[4]
 george.iloc['1968']
george.iloc[0:3] # slice
george.iloc[[0,1]] # list
george.loc['1968':] # slice
george.loc['1970']
george.loc[0]
george.loc[['1968', '1970']] # list<jupyter_output><empty_output><jupyter_text>Analogos ao loc e iloc* .at * .iat<jupyter_code>george_dupe = pd.Series([10, 7, 1, 22], index=['1968', '1969', '1970', '1970'], name='George Songs')
george_dupe
george_dupe.at['1970']
george_dupe.loc['1970']
george_dupe.iat[3]
george_dupe.loc['1970'].iat[0]<jupyter_output><empty_output><jupyter_text>**RESUMO** |Metodo | Quando usar || --- |--- |Acesso pelo atributo | Obter valores para um único nome de índice quando o nome é válidoAcesso pelo Indice | Obter / definir valores para um único índice.iloc | Obter / definir valores para uma posição .loc | Obter / definir valores para um label .iat  | Obter / definir valores para uma posição .at | Obter / definir valores para um label  |Slice | Resultado || --- |--- |0:1 | Primeiro item:1 | Primeiro item: -2 | Pegue do início ao penúltimo item::2 | Pegue do início ao fim, pulando todos os outros itens<jupyter_code>george
george.iloc[0:2]
george_dupe.iloc[::2]<jupyter_output><empty_output><jupyter_text>* Arrays booleanos<jupyter_code>mask = george > 7
mask
george + 2
george[mask]<jupyter_output><empty_output><jupyter_text>Metodos<jupyter_code>songs_66 = pd.Series([3, None , 11, 9],index=['George', 'Ringo', 'John', 'Paul'], name='Counts')
songs_66
songs_69 = pd.Series([18, 22, 7, 5], index=[ 'John', 'Paul', 'George', 'Ringo'], name='Counts')
songs_69<jupyter_output><empty_output><jupyter_text>* Iteration<jupyter_code>for value in songs_66:
    print(value)
for idx, value in songs_66.iteritems(): # unpacking 
    print(idx, value)
for idx in songs_66.keys():
    print(idx)<jupyter_output><empty_output><jupyter_text>* Overloaded operations<jupyter_code>songs_66
songs_66 + 2
songs_66
songs_69
songs_66 + songs_69
# conseguem identificar o problema acima? 
songs_66.fillna(0) + songs_69.fillna(0)<jupyter_output><empty_output><jupyter_text>* Getting and Setting Values |Metodo | Resultado || --- |--- |get(label, [default]) | Retorna um escalar, ou serie se o index é duplicado, para um label ou default caso o index não exista<jupyter_code>songs_66
songs_66['John']
songs_66.get('John')
songs_66.John # acesso ao nome do indice
songs_66.get('Fred') # sem retorno no get
songs_66.get('Fred', 'nao encontrado')
songs_66['John'] = 82 
songs_66
songs_66.John = 81
songs_66
ser = pd.Series([1, 2, 3, 4], index=['normal', 'median', 'class', 'index']) # atencao as palavras reservadas
ser<jupyter_output><empty_output><jupyter_text>**Nota** cuidado ao dar nomes de palavras reservadas aos indices<jupyter_code>ser.normal = 4
ser.median = 5
ser.class = 6
ser.index = 7<jupyter_output><empty_output><jupyter_text>**Nota:** cuidado ao usar .label para inserir um novo valor<jupyter_code>import keyword
print(keyword.kwlist)
pd.__version__
songs_66.loc['Jhon'] = 80
songs_66
# songs_66.loc['John'] = 80
songs_66.iloc[2] = 82  # as vezes é mais seguro ir pela posicao
songs_66<jupyter_output><empty_output><jupyter_text>* Reset Index<jupyter_code>songs_66.reset_index()
songs_66.reset_index(drop=True)
songs_66.reindex(['Billy', 'Eric', 'George', 'Yoko']) # criar novos indices
songs_66.rename({'Ringo':'Richard'}) # renomear um index
songs_66.rename(lambda x: x.lower()) # alterar todos os indices<jupyter_output><empty_output><jupyter_text>* Counts<jupyter_code>songs_66 = pd.Series([3, None , 11, 9], index=['George', 'Ringo', 'John', 'Paul'], name='Counts')
songs_66
scores2 = pd.Series([67.3, 100, 96.7, None, 100], index=['Ringo', 'Paul', 'George', 'Peter', 'Billy'], name='test2')
scores2
scores2.count()
scores2.value_counts()
scores2.unique()
scores2.nunique()
scores2.drop_duplicates()
scores3 = pd.Series([67.3, 100, 96.7, None, 100, 79], index=['Ringo', 'Paul', 'George', 'Peter', 'Billy', 'Paul'])
scores3
scores3.groupby(scores3.index).first() # or last<jupyter_output><empty_output><jupyter_text>* Statistics<jupyter_code>songs_66.sum()
songs_66.mean()  # metodo
songs_66.median() # em distribuições nao normais, a mediana é muito util, 
songs_66.quantile()
songs_66.quantile(.9)
songs_66.describe()
songs_66.describe(percentiles=[.05, .1, .2])
songs_66.min()
songs_66.idxmin()
songs_66.max()
songs_66.idxmax()
songs_66.cumsum()
songs_66.cumprod()<jupyter_output><empty_output><jupyter_text>* Conversão de tipos<jupyter_code>songs_66.round()
songs_66.clip(lower=80, upper=90)
songs_66.astype(str)
songs_66.astype(str).max()
pd.to_numeric(songs_66.astype(str), errors='coerce')<jupyter_output><empty_output><jupyter_text>* None<jupyter_code>songs_66.fillna(-1)
songs_66.dropna()
val_mask = songs_66.notnull()
val_mask
songs_66[val_mask]
nan_mask = songs_66.isnull()
nan_mask
songs_66[nan_mask]
songs_66.isnull() 
~songs_66.notnull()<jupyter_output><empty_output><jupyter_text>* Operações de Matrizes<jupyter_code>songs_66.dot(songs_69) # calcular o produto 
songs_66.dropna().dot(songs_66.dropna()) # evitando problemas com NA
songs_66.T
songs_66.transpose()<jupyter_output><empty_output><jupyter_text>* Append, combining, and joining two series<jupyter_code>songs_66.append(songs_69)
songs_66.append(songs_69, verify_integrity=True)
def avg(v1, v2):
    return (v1 + v2)/2.0
songs_66.combine(songs_69, avg)
songs_69
songs_66.update(songs_69) # anomalia: faz o update mas não retorna nada 
songs_66
songs_69.repeat(2)
songs_69.sort_values()
s = pd.Series([2, 2, 2], index=['a2', 'a1', 'a3'])
s.sort_values(kind='mergesort')
songs_66.sort_values(ascending=False)
songs_66.sort_index()
songs_66.rank()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1347871781397793, 'fraction_numerical': 0.04930811000175162, 'mean_word_length': 6.875172413793104, 'pattern_counts': {'":': 0, '<': 135, '<?xml version=': 0, '>': 136, 'https://': 6, 'lorem ipsum': 0, 'www.': 3, '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': '33', 'dir': 'jupyter-structured-clean-dedup', 'id': '652807', 'license': 'MIT', 'n_tokens_mistral': 4856, 'n_tokens_neox': 4452, 'n_words': 1041, 'path': 'aulas/para_alunos_aula_04/Aula_03_Bibliotecas_Pandas_Series.ipynb', 'repo_name': 'flaviobp/python-analise-dados', 'size': '11427'}