Open gabrielbdornas opened 1 month ago
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
@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?
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/
@labanca, achei este issue no repo dpm
. Será que Francisco já estava pensando algo neste sentido?
@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.
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.
@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?
@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 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
.
See #148.