top of page

do R para o python: Pandas

Se você chegou aqui sem ver o primeiro post, te indico ir lá dar uma breve olhada, mas se você já tem o jupyter instalado em sua máquina e consegue utilizar na IDE RStudio, let's bora que esse é o segundo post da minha série do R para o python.


Antes de tudo precisamos falar sobre o base de dados que vamos utilizar no decorrer desse post.


Base de dados, Fê?

Sim! Eu espero que você consiga me acompanhar nos códigos, erros e acertos durante todo o nosso percurso da saga de aprender a linguagem python e, por isso, a base de dados deve ser de livre acesso e tenha abertura para que você tenha seus próprios insights.


A base de dados que vamos utilizar é a:



Essa base de dados é considerada como o principal acervo de dados sobre cinema e TV no mundo, disponível gratuitamente online, mas você pode baixar essa base de dado em vários formatos aqui. Este link irá te levar ao meu GitHub com os dados dessa base em diversos formatos. Escolha a que mais faz sentido para você, mas em nosso primeiro estudo utilizamos ela em formato .csv.


Ai Fê, quero aprender outra forma de conseguir baixar ou ter acesso a essa base de dados, tem como?

Tem sim! Uma das formas mais elegantes e que vai lhe trazer um grande aprendizado, é utilizar o site da Base dos Dados. Você vai aprender a se conectar com o BigQuery e "só" isso já é incrível para quem está aprendendo análise de dados. Não conseguiu? Vou ensinar no terceiro post como se conectar e pivotar dados com o pandas e, claro, para seguir aqui comigo, baixa lá do meu GitHub e não se estressa agora.


Com a base de dados na sua máquina...


Vamos começar?

O primeiro passo é importar a biblioteca mais ovacionada nessa terra do python, a bliblioteca pandas.


Saiba mais em Wikipedia
Saiba mais em Wikipedia

Em programação de computadores, pandas é uma biblioteca de software criada para a linguagem Python para manipulação e análise de dados. Em particular, oferece estruturas e operações para manipular tabelas numéricas e séries temporais. É software livre sob a licensa licença BSD. O nome é derivado do termo inglês "panel data", um termo usado em estatística e econometria para conjunto de dados que incluem várias unidades amostrais (indivíduos, empresas, etc) acompanhadas ao longo do tempo.


Suas principais características nesse universo é facilidade em manipular dados:


  • Uso do objeto "DataFrame" para manipulação de dados, com indexação integrada.

  • Ferramentas para ler e escrever dados entre diferentes estruturas de dados e formatos de arquivo.

  • Alinhamento de dados e manipulação de dados ausentes.

  • Reformatação e pivoteamento de matrizes (dados).

  • Divisão (slicing), fancy indexing, e subsettingde grandes conjuntos de dados.

  • Inserir e deletar colunas em conjuntos de dados.

  • Ferramentas para fundir(merging) ou juntar(join) conjuntos de dados.

  • funcionalidade para séries temporais (time series): Geração de intervalo de datas(date range) e conversão de frequência, estatística móvel, regressão linear, entre outras.

  • Filtração e limpeza de dados.



É um montão de coisa, né?

Pois é, para cada coisinha dessa existe uma função dentro dessa biblioteca, ou seja, ao importar a biblioteca pandas lembre-se que todas as suas funções serão carregadas. E, caso você esteja usando o linux (é o meu caso), pode ser que você se depare com o seguinte erro ao tentar importar essa biblioteca:


Error in py_call_impl(callable, call_args$unnamed, call_args$named): Evaluation error: ModuleNotFoundError: No module named 'pandas'
Run `reticulate::py_last_error()` for details..
Erros durante o embrulho: Evaluation error: ModuleNotFoundError: No module named 'pandas'
Run `reticulate::py_last_error()` for details..
Error: no more error handlers available (recursive errors?); invoking 'abort' restart

Para resolvê-lo, você deve ir ao terminal e seguir com o seguinte comando,


pip install pandas

Instalou?

No R, vamos importar a biblioteca de interesse. Na sua linha de comando, digite:


import pandas

Se você não conseguiu instalar, volte no ponto de preparação. Este ponto de preparação do ambiente foi feito no primeiro post.


Perde tempo não e vai lá, tá?

Para quem conseguiu importar a biblioteca pandas...


Vamos ler a base de dados através da biblioteca pandas, que diferente do R, todas as opções de leitura de dados se encontram nessa mesma biblioteca. Veja, no código abaixo, que o sinal de atribuição na linguagem python é o "=".


df = pandas.read_csv("dados/imdb.csv")
type(df)

É importante dizer que essa leitura de base de dados é mais fácil que você vai ver (rs). E, como em qualquer linguagem, é bem possível que você se depare com alguns erros de encoding, de separadores ou decimais. A função read_csv do pandas possui parâmetros para especificar cada um desses problemas com o intuito de solucioná-los e lhe ajudar a fazer essa leitura corretamente. Veja a documentação da função.


E você Rzeire deve estar se perguntando:


O python não tem o help? Documentação pra quê?

Há um help assim como há no RStudio, mas o python que me desculpe, esse help é bem ruim e, por isso, quem utiliza essa linguagem tem o costume de acessar a documentação das bibliotecas para entender erros e parâmetros. Testa aí e me diz nos comentários o que você achou.


help(pandas.read_csv)

É crucial que no início de qualquer análise saber o tipo de dados que estamos trabalhando.


É série temporal? É dados longitudinais? Coorte?

O comando type() nos traz como resultado que essa base de dados é da seguinte classe,


<class 'pandas.core.frame.DataFrame'>

e, assim como no R, essa classe de dados nos permite "brincar" aos montes com suas variáveis.


Vamos ver essa base de dados?

Na linha de comando, digite 'df', visto que esse é o nome da base de dados:

df

Uma coisa ruim do python é que colocar somente o nome 'df' que atribuímos a base de dados nos retorna um resultado nada informativo. Não sei dizer se em outras IDE o resultado é diferente desse, mas como estamos utilizando a IDE RStudio, digo que essa visualização não é uma das melhores, visto que queremos, no mínimo, ver todas as variáveis. Outra coisa que não consegui fazer, foi visualizar a base de dados através do Environment. Ela se torna uma longa lista e não é nada intuitivo.


E a gente faz o quê, Fer?

A gente lida com isso e tenta melhorar essa saída com outras funções já existentes. Uma dessas funções é a describe(). Veja a sua saída na imagem a seguir.


df.describe()
Elaborado pela autora
Elaborado pela autora
Ajudou no nosso problema?

Não! Mas já sabemos que a função describe() retorna o mesmo que o nosso queridinho summary() do R. Outra opção interessante dessa função é o parâmetro 'include' e, claro, há várias formas de solicitar essas medidas de posição, como selecionar só as variáveis categóricas, excluir as object e tudo isso vai depender da leitura da documentação da função. Mas, como a gente brilha MUITO nessa IDE, é só você selecionar a função e dá um F1 que você terá acesso a alguns exemplos de aplicação da função na aba Help. Faz aí e me conta se gostou nos comentários.


df.describe(include='all')

E atente-se: Como default o python joga 'fora" todos os NA's quando usamos essa função.


Para saber o nome e tipo de classe das variáveis,


df.info()

Elaborado pela autora
Elaborado pela autora

No pandas, os tipos de dados são chamados de dtypes. Cada coluna em um DataFrame pode ter um dtype diferente, dependendo do tipo de dados que ele contém. Os dtypes comuns no pandas incluem int , float , object , datetime e bool.


E o que é esse dtype object, Fê?

Isso significa que tem alguma observação fora do padrão implicando na não identificação do dtype da variável/coluna. Isso é muito comum em observações que contem datas e, já sabemos desde o R, o quanto é trabalhoso 'arrumar' essa confusão. Vamos focar um pouco nessa etapa mais a frente.



Vamos juntes brincar com esses dados?

Com a base de dados, conseguimos aplicar qualquer função básica de interesse. Veja exemplos abaixo:


df.orcamento.mean()
Elaborado pela autora
Elaborado pela autora
df.receita.sum()
Elaborado pela autora
Elaborado pela autora

Os resultados não são tão bonitinhos, visto que ambas as variáveis estão no dtype ou classe float64 e não estamos aplicando alguma função para arrendondar em um número x de casas decimais. Veja também que aqui não temos o nosso queridinho operador pipe (%>% ou |>).


Como é que essa galera vive sem um pipe?

Eu também não sei, mas o ponto é utilizado para separar módulos, ou como parte de um atributo ou método de uma classe. Ainda não identifiquei uma diferença entre a ideia do pipe e do ponto utilizado no python, pois ambos possuem a mesma forma de encadeamento das funções que serão utilizadas.


Mas Fê, e se eu quiser aplicar a função em mais de uma variável, como vai ser isso?

Calma! Tem tudo para dar certo e nós vamos aprender juntes isso daí. E, para aprender a manejar muitas variáveis, é uma consequência ter que lidar com os verbos ou funções dessa biblioteca pandas. Há vários verbos, mas a ideia aqui é ir contando uma historinha com os dados e sentindo a necessidade de utilizar cada um desses verbos.


Vou tentar trazer um/dois exemplos de cada um desses verbos, tá?

Começando com um bem simples...


Vamos ordenar linhas?

Para ordenar linhas podemos utilizar sort_index() ou sort_values(). No decorrer dos códigos vamos ir criando novos DataFrame para que a base de dados inicial não seja alterada.

O desejo é ordenar a base de dados de dados de acordo com o ID do filme e nós temos essa variável.


Elaborado pela autora
Elaborado pela autora
df2 = df.sort_values('id_filme')
df2

Veja que a base de dados foi ordenada do menor para o maior (ascendente), o que implica que o default dessa função a ordenação já é de forma ascendente. Observe também que a variável está entre aspas simples, mas nada te impende de usar as aspas duplas, pois ambas vão funcionar. Caso o interesse seja ordenar a base de dados por duas ou mais variáveis, esse código vai mudar.



Para qualquer operação em mais de duas variáveis é necessário utilizar os colchetes [-] e, claro, o nome das variáveis devem estar delimitadas por ',' e entre aspas simples ou duplas, conforme código abaixo.

df2 = df.sort_values(['id_filme', 'ano'])

E se você não percebeu...

É muito falado que na linguagem python o índice se inicia no 0 e, por isso, ao solicitar a ordenação pelo índice a primeira linha será a 0. E, caso seja de interesse, podemos ordenar de forma decrescente, visto que o default é na ordem crescente. Para isso basta utilizar dentro da função sort_index o parâmetro ascending = False.


df3 = df.sort_index(ascending = False)

E você pode pensar, mas o sort_values também não faz isso? FAZ SIM e é dessa forma aí abaixo.


df4 = df.sort_values(['id_filme', 'ano'], ascending = False)

Fê, eu não quero ordenar, quero só selecionar variáveis, como é que é?

No python é possível selecionar linhar e colunas das seguintes formas:


  • Utilizando colchetes:


Na minha opinião essa forma é ruim, visto que a gente precisa saber o nome da variável bem certinho para não dar erro.


df["ano"]

E como você pode ver, a variável ano está como float64, o que implica ter esse .0 ao final.


Vamos trocar isso rapidinho?


Vamos selecionar a variável com o colchetes e já aplicar as funções fillna() e astype(). A função fillna() é utilizada para alterar os valores ausentes em algum número ou caracter de interesse, já a função astype() é utilizado para converter um objeto pandas em um dtype especificado. É como se fosse no as.numeric, as.character do R, a diferença do python é que a função é a mesma e o seu parâmetro que muda.


df["ano"].fillna(0).astype(int)

Não quer tratar os NA's agora?

Então vamos utilizar somente astype('Int64').


df["ano"].astype('Int64')

E qual é a diferença do astype('Int64') do astype(int)?

Um consegue lidar com valores ausentes e o outro não. O erro apresentado quando utilizamos astype(int) sem tratar valores ausente é esse abaixo.


Error in py_call_impl(callable, call_args$unnamed, call_args$named): 
Evaluation error: pandas.errors.IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer

Mas, nesse momento nós estamos estudando o pandas, então vamos utilizar o pandas também para fazer essa transformação de classes. A função to_numeric() pode ser útil para reduzir o uso de memória ao escolher o tipo inteiro mais eficiente.


pandas.to_numeric(df["ano"], downcast="integer")

Fer, que parâmetro é esse aí? Downcast?

Esse parâmetro economiza memória ao escolher automaticamente entre int8, int16 ou int32, em vez de um int64 desnecessário, o que ocorre quando usamos a função astype(). Ele também evita valores inesperados ao converter corretamente os números sem arredondamentos. Mas, é importante ressaltar que:


Se quiser garantir que NaN sejam preservados, use df["ano"].astype('Int64'), pois pd.to_numeric() converte NaN para float64.

  • Utilizando a função .loc

Esse método é estilo o que usamos no R. Veja que estamos usando o ': ' como indexador das linhas.

df.loc[:, ['ano']]

  • Utilizando a função .iloc


Essa função é A M-E-L-H-O-R para os Rzeires (na minha opinião), pois utilizamos as posições das variáveis. Veja só:


df.iloc[:, 2]

A ideia é selecionar mais colunas? Podemos fazer por posição das variáveis do DataFrame:


df.iloc[:, 1:4]

E como selecionar as linhas?

Acredito que agora tenha ficado mais fácil. Veja o exemplo abaixo:


df.iloc[1:10, 1:4]

Outra forma de selecionar colunas é em forma de atributo. Se vê muito em tutoriais essa forma de seleção de colunas.


df.ano[1:10]

Cansei de selecionar colunas e linhas, agora quero criar colunas, como é que faz isso Fê?

Criar colunas do zero eu acho um pouco mais difícil no dia a dia de uma analista/cientista de dados, a não ser que esteja criando KPI's ou coisas do tipo, mas o que fazemos MUITO é criar variáveis a partir de variáveis já existentes na base de dados. Nessa etapa eu fui aprender com o Programação Dinâmica e deu muito certo.


Vamos juntes?

Antes de qualquer coisa, vamos aplicar alguma função que faça essas variáveis da classe float se tornarem classe int, mas todas de uma vez só para não perder tempo e não sobrecarregar a sua mente, como fiz com a minha.


Eu achei incrível a forma que o python atua com essa necessidade e acabei aprendendo duas novas funções. As funções select_dtypes, apply e a partir dela o parâmetro errors = "coerce" que transforma a notação científica em um número normal. Essa transformação foi necessária devido a algumas variáveis estarem em notação cientifica, o que é bem comum.


Como tratativa de float, -inf ou inf, vamos alterá-las para NA (valores ausentes). Com esse objetivo acabei conhecendo a função replace() que é idêntica a do R. Te convido a dar um F1 nessa função para entender um pouco mais os seus parâmetros.


df.replace([float("inf"), float("-inf")], pandas.NA, inplace=True) 

Uma das formas de encontrar as variáveis da classe float na base de dados é utilizando a função select_dtypes, sendo assim:


 print(df.select_dtypes(include="float").columns)
 for col in df.select_dtypes(include="float").columns:
     print(f"\nColuna: {col}")
     print(df[col].unique())  # Verifica valores únicos

As variáveis float são:


  • ano

  • orcamento

  • receita

  • receita_eua

  • nota_imdb

  • num_criticas_publico

  • num_criticas_critica


Coloquei elas em um vetor para facilitar o manuseio com o seguinte código:


colunas_float = ["ano", "orcamento", "receita", "receita_eua", "nota_imdb", "num_criticas_publico", "num_criticas_critica"]

Temos 3 formas de fazer essa operação e você pode encontrar por aí cada as três formas, tudo depende de como você programa. São elas:


  • Jeito intuitivo


df[df.select_dtypes(include="float").columns] = df.select_dtypes(include="float").apply(pandas.to_numeric, errors = "coerce").round(0).astype("Int64")

  • Mais inteligente


df[colunas_float] = df[colunas_float].apply(pandas.to_numeric, errors = "coerce").round(0).astype("Int64")

  • Expert


df[df.select_dtypes(include="float").columns] = (   df.select_dtypes(include="float")
   .apply(pandas.to_numeric, errors = "coerce")
   .round(0)
   .astype('Int64')
   )

E você deve estar se perguntando:


Fer, porquê você usou o .astype após aplicar o pandas.to_numeric e o .round antes do .astype?

Eu gosto de dizer que a programação orientada a objeto é uma escadinha e para ir subindo/descendo precisamos passar por etapas para não cair. A função astype("Int64") possui alguns requisitos para conseguir converter float ou o que desejar para inteiro, logo se houver:


  • Valores decimais reais (12.34, 56.78)

  • Strings ocultas ("unknown", "N/A") misturadas na coluna

  • Valores infinitos (inf, -inf) não removidos corretamente


Vai dar E-R-R-O, pois estes casos não conseguem ser convertido e, por isso, precisamos dessas tratativas. Já o pandas.to_numeric(errors="coerce") converte valores para número e transforma erros em NaN.


Observando a base de dados após a manipulação e é possível verificar que deu tudo certo.


Elaborado pela autora
Elaborado pela autora
df.dtypes

E essa data_lancamento?

type(df["data_lancamento"])

Veja que no dtypes essa variável se encontra como object e classe pandas.core.series.Series. Precisamos que ela seja considerada como date/data (Poxa! Se data no R já dava trabalho, espero que o python nos ajude de uma forma melhor em relação a isso.).


Com o pandas podemos utilizar a função bem intuitiva que é to_datetime para converter essa variável.





Vamos testar a alteração de classe dessa variável com o seguinte código:


df['data_lancamento'] = pandas.to_datetime(df['data_lancamento'])

Mas, veja que dá um errinho aí...

Error in py_call_impl(callable, call_args$unnamed, call_args$named) : Evaluation error: ValueError: time data "1990" doesn't match format "%Y-%m-%d", at position 29. You might want to try:
    - passing `format` if your strings have a consistent format;
    - passing `format='ISO8601'` if your strings are all ISO8601 but not necessarily in exactly the same format;
    - passing `format='mixed'`, and the format will be inferred for each element individually. You might want to use `dayfirst` alongside this.

Ele reclama do formato da nossa data que é "%Y-%m-%d" e nos dá a informação de que algumas observações que estão nesse formato. E sim, nós temos casos em que temos somente o ano e não a data por inteiro.


O que vamos fazer?

Podemos resolver isso identificando e substituindo as linhas que contêm apenas o ano por NaN, visto que, no primeiro momento, é o que faz sentido. A brincadeira com datas sempre fica séria e vejo que aqui teremos que trabalhar com strings e regex para identificar esse padrão do ano (YYYY) e, como estamos no python, procurei uma library para isso e me indicaram um tal de re.


A biblioteca re do Python é uma biblioteca padrão que permite trabalhar com expressões regulares. Com ela, é possível procurar padrões de texto, como todas as palavras que começam com "a" ou todas as frases que terminam com "!".

Vamos importar essa biblioteca?

import re

Com ela importada (espero que não tenha dado algum erro), vamos alterar aquelas observações com apenas 4 dígitos para valores ausentes utilizando as bibliotecas re e pandas.


No primeiro momento vamos iniciar uma nova variável para não misturar e perdermos a originalidade da variável.


data_lancamentoN2 = "data_lancamento"

A variável mascara_anos que será criada, é a variável que vai identificar os quatro dígitos com o regex. Veja no código abaixo que utilizamos a função str.match() para encontrar esse padrão e, claro, antes de aplicar uma função de string devemos torná-la ou ajustá-la para uma string com a função as.type().


mascara_anos = df[data_lancamentoN2].astype(str).str.match(r"^\d{4}$")

Agora temos que localizar as linhas que estão com o padrão já descrito na variável e adicionar NA.


df.loc[mascara_anos, data_lancamentoN2] = pandas.NA

Desde o princípio a ideia era apenas mudar a classe da variável data_lancamento de object para date, que é a sua devida classe e nos possibilitará extrair dia, mês, ano e conseguir efetuar algumas operações ao longo desse post.


df[data_lancamentoN2] = pandas.to_datetime(df[data_lancamentoN2], errors="coerce").dt.date

Vale ressaltar que usamos o parâmetro errors="coerce" em funções como pd.to_numeric() ou pd.to_datetime() pois estes substituem os valores inválidos por NaN (Not a Number) ou NaT (Not a Time), em vez de gerar um erro.


Será que deu certo?

Vamos dar um print nessa variável.

print(df[data_lancamentoN2].head())

E a quantidade de valores faltantes existentes? É possível contar? É SIM!


valor_na = df['data_lancamentoN2'].isna().sum()

Me diz aí nos comentários quantos valores temos.


Se fosse no R, provavelmente eu iria utilizar a função case_when ou o nosso querido ifelse, mas aqui no python temos outra opção: utilizar a função where da biblioteca numpy (vamos estudar ela no próximo post).


Seria mais fácil para os Rzeires?

Talvez sim e talvez não, mas podemos testar rapidinho.


import numpy
data_lancamentoN3 = "data_lancamento"
df[data_lancamentoN3] = numpy.where(mascara_anos, pandas.NA, df[data_lancamentoN3])

print(df[data_lancamentoN3].head())

Acredito que já deu pra entender que a lógica de criação de variáveis é bem parecida com a do R e isso já é mais do que meio caminho andado no universo de ETL. E se tem uma coisa que sabemos, é que sumarizar dados é uma parte BEMMM importante na descritiva dos nossos dados.


Bora sumarizar?

Tentar trazer medidas de posição já é um ótimo início, mas muitas vezes queremos agrupar a base de dados por alguma variável e assim somar algum valor, fazer a média para entender algum comportamento ou simplesmente contar o número de observações.


No R nós temos o super, marter queridinho dplyr e suas funções que são incríveis, no python, já é um pouco diferente. Conhecendo a nossa base de dados, vamos selecionar algumas de interesse para essa brincadeira. Elas são:


  • genero

  • ano

  • duracao

  • nota_imdb

  • receita

  • orcamento


Com essas variáveis vamos conseguir trabalhar algumas funções importantes no universo dos dados e explorar ainda mais a biblioteca pandas.


Vamos entender um pouquinho sobre elas?

Para não ter que ficar escrevendo uma a uma toda vez, vou inseri-las em um vetor:


variaveis_summ = ["generos", "ano", "duracao", "nota_imdb", "receita", "orcamento"]

Como vamos utilizar somente elas, nada mais junto do que ter uma base de dados somente com as variáveis de interesse, evitando carregar uma grande massa de dados desnecessários para a nossa sumarização.



df_summ = df.filter(variaveis_summ)

Obtendo mais informações sobre as variáveis:


df_summ.info()

A gente pode ver que a variável duracao está em minutos e generos está como object, mas nós queremos trabalhar com elas em hora e fatores, para isso precisamos aplicar alguma transformação. No R, usaríamos o mutate, mas aqui vamos usar a função assing.


A função .assign() do Pandas é usada para adicionar novas colunas ao DataFrame de forma funcional, ou seja, sem modificar o DataFrame original, a menos que você o reatribua.

Utilizando essa função:


df.assign(
  duracao_h = df.duracao/60).filter(["duracao_h"])

Eu simplesmente A-D-O-R-E-I essa função (rzeire nenhum vai dizer o contrário rs). É simples, é fácil de entender e aplicar.


O que aprendemos com esse exemplo?

Que criar variáveis a partir de variáveis já existentes na base de dados não é muito trabalhoso, desde que elas estejam em suas devidas classes e, quem sabe, com valores ausentes já tratados.


Mas vamos lá... A primeira pergunta que eu tenho é:


Quantos gêneros nós temos?

A função .unique() é uma D-I-V-A em qualquer linguagem. No R nós temos a distinct, mas é fácil desenrolar no python com a unique. A saída do código abaixo é um array gigante e, por isso, acompanha utilizando os códigos em sua máquina que é sucesso.


df["generos"].unique()

Parece ser um tantinho bom, mas:


Quantos filmes temos em cada um deles?
df["generos"].value_counts()
print(df["generos"].value_counts().head(30))

É, já vimos que há muitos gêneros que aparecem apenas uma vez.


Vamos ver quantos eles são?

Execute o código abaixo para entender os insigths que conseguimos trazer com essa descritiva tão poderosa.


df["generos"].value_counts()[df["generos"].value_counts() == 1]

Poxa! De 874 gêneros, 288 aparecem somente uma vez, indicando que 33% dos filmes são gêneros pouco produzidos. E aí vem o questionamento: Será que estes gêneros lucram? A parte ruim é que temos muitos valores ausentes, totalizando 73% da amostra com NA, mas para "brincar" vamos continuar com essa ideia.


df["receita"].isna().sum()

Neste caso, usaríamos a função mutate e case_when para criar a variável lucro e depois uma dummy com o indicativo de lucro ou não. No python nós seguimos com a função assign.


df = df.assign(
    lucro = lambda x: x["receita"] - x["orcamento"])
#Tratativa para os valores faltantes para que possamos seguir com a ideia de aprendizagem.
df["lucro"] = df["lucro"].fillna(0).astype(int)
df = df.assign(    
    lucro_cat = lambda x: numpy.select(
        [x.lucro >  1000000, x.lucro > 0, x.lucro.isnull()],
        ["Mais de milhão", "Pouco", "Sem info"],
        "Não lucrou"
    )
  )

Outra função que também pode ajudar com essa mesma ideia de ifelse é a função where:


df.assign(
        lucro_cat = lambda x: numpy.where(
            x.lucro > 1000000,
            'lucro',
            numpy.where(
                x.lucro > 0,
                'pouco lucro',
                numpy.where(
                  x.lucro.isnull(),
                  'sem info',
                'neutro'
    )))
)
#df = df["lucro"].fillna(0)

Queria dizer que "apanhei" com os erros que tive para conseguir construir essa nova variável. Infelizmente, a saída de erros do python não são esclarecedoras como o R é, mas consegui entender que algumas funções não conseguem trabalhar com NA's e essas funções não possuem parâmetros para atribuir o que fazer nesses casos, como nos temos no R na função mean com o na.rm.


Mas e aí, temos muitos lucros?

Conseguimos contar o número de cada categoria com a função value_counts:


df.lucro_cat.value_counts()

Poderíamos "brincar" com essa base de dados e ter insights bem legal somente com descritivas de sumarização de dados, mas este não é o meu intuito nesse momento.


No R usamos o summarize junto ao group_by para conseguir medidas por agrupamentos de interesse e é bem fácil entender a dinâmica da utilização dessas duas funções que são MUITO utilizadas em nosso dia a dia como analistas.


Bora pensar em fazer isso com o python?

Observando todo o contexto que construímos até aqui, acredito que seria interessante observar em qual gênero está concentrado (ou não) os maiores lucros, receita e orçamento. Para isso, vamos utilizar a função groupby e a agg.


A função .agg() no Python é usada para agregar valores de colunas em um DataFrame ou Series, aplicando funções estatísticas ou personalizadas. Ela é muito útil no pandas quando queremos resumir ou transformar dados de diferentes maneiras.

O .groupby() segue três etapas principais:


  1. Divisão – Os dados são separados em grupos com base em uma coluna.

  2. Aplicação – Aplicamos uma função estatística ou personalizada sobre cada grupo.

  3. Combinação – O resultado é reunido em um novo DataFrame ou Series.


Vamos lá?

Primeiro vamos entender como funciona o .groupby() e para isso vou criar uma base de dados com o agrupamento:


df_by = df.groupby("generos").count()

Veja que a lógica é idêntica a do group_by + summarise do R. Na saída acima temos a quantidade de filmes por gênero. Não deixe de fazer na sua máquina para conseguir acompanhar o raciocinio.


Vamos fazer o lucro por gênero?

df.groupby("generos")["lucro"].sum()

Viu que é mais parecido com o R do que a gente imagina? Mas agora eu quero olhar somente para o ano de 2020.


df[df["ano"] == 2020].groupby("generos")["lucro"].sum()

Temos alguns valores negativos, o que nos leva a pensar que alguém aí foi irresponsável, gastou muito mais do que tinha e podia. Mas, porém, contudo e todavia, acredito que um filme possa ter mais de uma forma de fomento e, por isso, eles podem ter torrado mais dinheiro e isso foi contabilizado somente como gasto e não receita. Cuidado com as correlações espúrias.


Você conseguiu ver que até o momento usamos duas formas (ou funções) para filtrar a base de dados?

Usamos o sinal de == e a função .filter. Cada uma foi utilizada em um contexto diferente. O do filter foi esse:


(df = df.assign(
    lucro = lambda x: x["receita"] - x["orcamento"])).filter(["receita", "orcamento", "lucro"])

E o == foi:


df[df["ano"] == 2020].groupby("generos")["lucro"].sum()

Eles são usados para propósitos diferentes no Pandas, e cada um tem sua aplicação específica. A função .filter() não é usada para filtrar linhas! Ela serve para selecionar colunas ou índices com base em critérios. Já o == filtra linhas com base em valores.


E Fê, e aquela função .agg lá que você tinha comentado?

É verdade, vamos falar sobre ela. Como estávamos falando da função .groupby, com o .agg() podemos aplicar múltiplas funções ao mesmo tempo. Isso é incrível e vem de encontro com a ideia do summarise do R. Vamos de exemplo:


Nós somamos os lucros, que não deixa de ser uma medida ruim, mas vamos analisar a média, moda e desvio padrão por gênero e para aqueles que possuem um lucro diferente de 0.


lucro_metricas = df[df["lucro"] != 0] .groupby("generos").agg(
                   total_lucro=('lucro', 'sum'),
                   avg_lucro=('lucro', 'mean'),
                   num_count=('id_filme', 'count')
)

É incrível, né?

Essas funções são bem similares aos que nós, Rzeires, estamos acostumades a fazer no dia a dia de qualquer descritiva, seja acadêmica ou não e é por issoque o gostinho fazer esse paralelo com as funções do R traz uma leveza nos estudos dessa linguagem.


Por enquanto, foi assim que iniciei os meus estudos do python. Para alguns já é um ótimo conhecimento, mas ainda falta a parte de pivotagem e um bom aprofundamento na biblioteca numpy.


Fer, você vai falar sobre pivotagem utilizando a biblioteca pandas?

SIMMMMM! Vou falar sobre pivotagem no step_2 dessa série que estou amando desbravar. E, se você chegou até aqui, tenho dois desafios para te fazer:


  1. Para uma pessoa programadora que observa muito bem o que está fazendo, a utilização de funções sem explicação não é algo muito interessante, mas eu deixei para vocês a parte de estudar sobre o parâmetro lambda. Ele foi utilizado junto a função .assign.

  2. Veja o exemplo abaixo e replique (não precisa ser igual) para o contexto da nossa base de dados. A partir desse post você tem TODAS as razões para conseguir.

# customer_metrics = df1.groupby('customer_id').agg(

# total_spent=('amount', 'sum'),

# avg_purchase_value=('amount', 'mean'),

# num_purchases=('purchase_id', 'count'),

# most_frequent_category=('category', lambda x: x.mode()[0] if not x.mode().empty else None)

# ).reset_index()

#

# df1['month'] = df1['purchase_date'].dt.to_period('M')

#

# monthly_trends = df1.groupby('month').agg(

# total_sales=('amount', 'sum'),

# avg_purchase_value=('amount', 'mean')

# ).reset_index()


Te vejo no próximo post?

Espero que tenha gostado e que realmente volte.


Até mais!


Fernanda Kelly R. Silva | Estatística


Comments


Em um mundo onde tudo pode ser monitorado e medido, os dados são apenas a matéria-prima do conhecimento.

© 2020 - 2025 por

Fernanda Kelly R. Silva.
 

Em um mundo onde tudo pode ser monitorado e medido, os dados são apenas a matéria-prima do conhecimento. 

Follow

  • git
  • Linkedin

Follow

Follow

bottom of page