Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Estudar a viabilidade de executar códigos R em Python #147

Open
gabrielbdornas opened this issue Oct 21, 2024 · 9 comments
Open

Estudar a viabilidade de executar códigos R em Python #147

gabrielbdornas opened this issue Oct 21, 2024 · 9 comments
Assignees

Comments

@gabrielbdornas
Copy link

gabrielbdornas commented Oct 21, 2024

See #148.

@labanca
Copy link

labanca commented Oct 21, 2024

Existe um pacote chamado rpy2 que se propõe a executar funções de pacotes R dentro de um programa python.

rpy2 is an interface to R running embedded in a Python process

No windows, somente consegui instalá-lo em um ambiente virtual no Python 3.10 e a versão do rpy2 sendo 3.5.12 (em versões mais recentes ocorreu erro de instalação):

py -3.10 -m venv venv
source venv/Scripts/activate
pip install rpy2==3.5.12

e o seguinte snipet foi executado utilizando o rpy2, definindo uma função R e recebendo o resultado dela no Python:

import rpy2.robjects as ro
from rpy2.robjects.packages import importr

# Load the R base package
base = importr('base')

# Load a specific R package (e.g., ggplot2)
ggplot2 = importr('ggplot2')

# Define an R function and use it
ro.r('''
my_function <- function(x) {
    return(x + 1)
}
''')

# Call the R function from Python
r_my_function = ro.globalenv['my_function']
result = r_my_function(5)
print(result[0])  # Output will be 6
output: 6.0

Também foi possível acessar pelo Python um dataframe criado no R (vetores e dataframes são objetos geralmente retornados pelo pacote relatórios):

import rpy2.robjects as ro
import pandas as pd
from rpy2.robjects import pandas2ri

# Activate the pandas2ri conversion
pandas2ri.activate()

# R code to create a data.frame
ro.r('''
    my_dataframe <- data.frame(
        column1 = c(1, 2, 3, 4),
        column2 = c('A', 'B', 'C', 'D'),
        stringsAsFactors = FALSE
    )
''')

# Access the R data.frame
r_dataframe = ro.globalenv['my_dataframe']

# Display the Python DataFrame
print(r_dataframe)
  column1 column2
1       1       A
2       2       B
3       3       C
4       4       D

Por fim, foi possível criar um dataframe no Python, passa-lo para o R e recuperá-lo. Em seguida é adicionada uma coluna a esse dataframe dentro do R e repassado esse dataframe modificado para o Python (convertendo-o também em um dataframe do pandas):

import pandas as pd
import rpy2.robjects as ro
from rpy2.robjects import pandas2ri

# Activate the conversion between pandas and R data frames
pandas2ri.activate()

# Step 1: Create a pandas DataFrame in Python
df_python = pd.DataFrame({
    'column1': [1, 2, 3, 4],
    'column2': ['A', 'B', 'C', 'D']
})

# Convert the pandas DataFrame to an R data.frame
r_dataframe = pandas2ri.py2rpy(df_python)

#step 2: Pass the dataframe to rpy2
ro.globalenv['r_dataframe'] = r_dataframe

# Step 3: Define and apply an R function
# (for example, here we're just returning the summary of the dataframe)
ro.r('''
    # R code that manipulates the dataframe
    result <- summary(r_dataframe)
''')

# Retrieve the result from R (could be a modified dataframe, list, or other object)
r_summary = ro.r('result')

# Prints the summary received from rpy2
print("-- Returned R dataframe summary --")
print(r_summary)

# Recover to Python the dataframe sent to R (note the type in python is <class 'rpy2.robjects.vectors.DataFrame'>)
r_dataframe = ro.r('r_dataframe')
print("\n-- R dataframe --")
print(r_dataframe)

# Adds a new line to the dataframe inside R
ro.r('''
    new_row <- data.frame(column1 = 5, column2 = "E")
    df_r <- rbind(r_dataframe, new_row)
''')

# receives dataframe with a new line and convert it back to pandas
pandas_dataframe = pandas2ri.rpy2py_dataframe(ro.globalenv['df_r'])
print("\n-- Pandas dataframe --")
print(pandas_dataframe)
-- Returned R dataframe summary --
['Min.   :1.00  ' '1st Qu.:1.75  ' 'Median :2.50  ' 'Mean   :2.50  '
 '3rd Qu.:3.25  ' 'Max.   :4.00  ' 'Length:4          '
 'Class :character  ' 'Mode  :character  ' None None None]

-- R dataframe --
  column1 column2
0       1       A
1       2       B
2       3       C
3       4       D


-- Pandas dataframe --
    column1 column2
0       1.0       A
1       2.0       B
2       3.0       C
3       4.0       D
11      5.0       E

@gabrielbdornas
Copy link
Author

@labanca, bastante interessante. Acredito que utilizar a versão 3.10 Python não seria um problema. Dou exemplo rápido da Frictionless, que exige a versão Python 3.8 ou superior.

Você chegou a montar/pensar em algum reprex para a chamada do pacote relatórios? Por falar nisso, ele fica em nossa organização (não achei nada em uma pesquisa rápida)? Poderia incluir o link aqui para mim?

@labanca
Copy link

labanca commented Oct 22, 2024

Você chegou a montar/pensar em algum reprex para a chamada do pacote relatórios?

Não sei se entendi a pergunta, seria montar um reprex com esses exemplos? Acho que se você estiver perguntando sobre criar um exemplo do pacote relatórios sendo usado pelo rpy2 , é o próximo passo.

Por falar nisso, ele fica em nossa organização (não achei nada em uma pesquisa rápida)? Poderia incluir o link aqui para mim?

Os pacotes ficam no bitbucket na conta da DCGF: https://bitbucket.org/dcgf/relatorios/src/master/

@gabrielbdornas
Copy link
Author

@labanca, achei este issue no repo dpm. Será que Francisco já estava pensando algo neste sentido?

@labanca
Copy link

labanca commented Oct 22, 2024

@labanca, achei este issue no repo dpm. Será que Francisco já estava pensando algo neste sentido?

Discutimos isso na época, mas depois não seguimos com a ideia. Não recordo os motivos ao certo, mas creio que havia demandas mais urgentes e essa abordagem era complexa.

@labanca
Copy link

labanca commented Oct 22, 2024

Outro update sobre o estudo.

É possível atribuir a um objeto Python um pacote, função ou código R e utilizá-lo no ambiente Python.

Com isso eu consegui instanciar o pacote relatórios no Python e aplicar uma de suas funções na base acoes_planejamento e receber o resultado dessa função do pacote relatórios no Python:

import rpy2.robjects as ro
from rpy2.robjects import pandas2ri
from rpy2.robjects.packages import importr
import unidecode


def clean_column_names(column_name):
    """
    Reproduce the LOA project data cleansing on the dataframe to be able to use relatorios functions
    """

    # Replace white spaces with dots
    column_name = column_name.replace(" ", ".")

    # Convert to unaccented equivalents
    column_name = unidecode.unidecode(column_name)

    # Convert to lower case
    column_name = column_name.lower()

    return column_name


# Activate the automatic conversion between R data frames and pandas DataFrames
pandas2ri.activate()
base = importr('base')
utils = importr('utils')
readr = importr('readr')

# You can convert a R package to a python object and use it in the python env
relatorios = importr('relatorios')

# Read the pipe-separated text file
acoes_planejamento = readr.read_delim("data/acoes_planejamento.txt", delim="|")

# Convert to pandas DataFrame
acoes_planejamento_df = pandas2ri.rpy2py(acoes_planejamento)

cleaned_column_names = [clean_column_names(name) for name in acoes_planejamento_df.columns]

acoes_planejamento_df.columns = cleaned_column_names

# LOA project names conventions (clunky)
nomes_esperados = [
    "codigo.do.programa", "nome.do.programa", "codigo.da.unidade.orcamentaria.responsavel.pela.acao",
    "codigo.da.funcao", "funcao", "codigo.da.subfuncao", "subfuncao",
    "codigo.do.tipo.de.acao", "tipo.de.acao", "codigo.da.acao", "titulo.da.acao",
    "codigo.do.identificador.de.acao.governamental..iag.", "exclusao.logica.da.acao",
    "finalidade.da.acao", "codigo.do.produto", "produto", "unidade.de.medida.do.produto"
]

novos_nomes = [
    "cod_prog", "nome_prog", "UO_COD",
    "cod_funcao", "nome_funcao", "cod_subfuncao", "nome_subfuncao",
    "cod_tipo_acao", "tipo_acao", "cod_acao", "titulo_acao",
    "cod_iag", "exc_acao", "final_acao", "prod_acao",
    "Produto", "unid_med_prod"
]

# Create a mapping from expected names to new names
name_mapping = dict(zip(nomes_esperados, novos_nomes))

# Rename the columns using the mapping, only if they exist in the DataFrame
acoes_planejamento_df.rename(columns=name_mapping, inplace=True)

# relatorios package need a column named ANO
acoes_planejamento_df['ANO'] = 2024

# Convert back to R DataFrame to apply the relatios function
acoes_planejamento_r = pandas2ri.py2rpy(acoes_planejamento_df)

# Assign the dataframe to R's global environment
ro.globalenv['acoes_planejamento'] = acoes_planejamento_r

# Call the function from the R package relatorios
result = relatorios.is_outros_poderes(ro.globalenv['acoes_planejamento'])

# Print the result
print(result)

O resultado é um vetor booleano, o que é o retorno das funções do pacote relatorios utilizadas para filtrar as bases:

[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
  [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
  [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
  [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
  [49] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
  [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
  [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

Há uma série de procedimentos que foram feitos para o exemplo que não necessariamente precisariam ser feitos. Como a base de dados ser lida utilizando o pacote readr do R e os nomes das colunas serem transformados para ficarem equivalentes aos nomes da LOA e PPAG, onde a função is_outros_poderes é geralmente utilizada.

@gabrielbdornas gabrielbdornas moved this from Todo to In Progress in Gestão à Vista Oct 23, 2024
@gabrielbdornas
Copy link
Author

@labanca, estou entendendo que a resposta para este Issue é SIM, conseguimos executar códigos R em Python. O que acha de fecharmos este Issue e já abrirmos um no dpm com a chamada para ação de criar esta funcionalidade lá?

Como já estão definidas várias atividades para o mês de novembro, isso poderá entrar como prioridade para dezembro.

O que acha?

@labanca
Copy link

labanca commented Oct 24, 2024

@gabrielbdornas estou bem empolgado com os resultados! Porém, acho que ainda temos de estudar um pouco mais. Tudo indica que é possível, mas um teste prático em alguma aplicação nossa seria mais seguro.

E no caso, quais seriam as funcionalidades em R que viriam para o DPM? Hoje o DPM que é cuido somente possui código Python.

@gabrielbdornas
Copy link
Author

@gabrielbdornas estou bem empolgado com os resultados! Porém, acho que ainda temos de estudar um pouco mais. Tudo indica que é possível, mas um teste prático em alguma aplicação nossa seria mais seguro.

E no caso, quais seriam as funcionalidades em R que viriam para o DPM? Hoje o DPM que é cuido somente possui código Python.

@labanca, concordo em fazer o teste em alguma aplicação. Alguma sugestão de qual?

Quanto às funcionalidades R que viriam para o dpm, estou entendendo que você não está se referindo somente ao pacote relatórios, mas sim tudo relacionado ao #148, correto? Se sim, acho que temos que pensar nesta conversa aqui para definir o que vai fazer sentido migrar para o dpm.

gabrielbdornas added a commit to splor-mg/reprex that referenced this issue Oct 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

No branches or pull requests

2 participants