Licença

This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.

License: CC BY-SA 4.0

Citação

Sugestão de citação: FIGUEIREDO, Adriano Marcos Rodrigues. Python no RStudio. Campo Grande-MS,Brasil: RStudio/Rpubs, 2020. Disponível em http://rpubs.com/amrofi/python_rstudio.

1 Introdução

Por que usaria o Python no RStudio?

As vezes alguém precisará utilizar o Python em algum procedimento e prefira realizá-lo por meio do RStudio (por exemplo por ter maior familiaridade com o RStudio e o RMarkdown). Ou não queira por algum motivo usar outras ferramentas tipo Jupyter ou PyCharm ou Spyder, comuns em aplicações para o Python.

Farei um uso misto entre R e Python neste RMarkdown. Esta aplicação usa o dataset (parte do pacote AER, data("CollegeDistance")) o qual contém dados de seção transversal da pesquisa High School and Beyond, realizada pelo Departamento de Educação em 1980, com acompanhamento em 1986. A pesquisa incluiu estudantes de aproximadamente 1.100 escolas secundárias. São dados contendo 4.739 observações em 14 variáveis - detalhes em https://rdrr.io/cran/AER/man/CollegeDistance.html. Portanto, inicio obtendo os dados pelo R, farei um modelo para comparação com a função lm e depois farei o mesmo pelo Python.

Detalhamento do dataset `CollegeDistance` do pacote AER. Fonte: https://rdrr.io/cran/AER/man/CollegeDistance.html

Detalhamento do dataset CollegeDistance do pacote AER. Fonte: https://rdrr.io/cran/AER/man/CollegeDistance.html

# ver em
suppressMessages(suppressWarnings(library(AER)))
data("CollegeDistance")
cd.d <- CollegeDistance
reg1 <- lm(education ~ score + unemp + wage + distance, data = cd.d)
summary(reg1)

Call:
lm(formula = education ~ score + unemp + wage + distance, data = cd.d)

Residuals:
    Min      1Q  Median      3Q     Max 
-3.7295 -1.1892 -0.2394  1.2238  5.4598 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)  9.377593   0.203510  46.079  < 2e-16 ***
score        0.095719   0.002663  35.949  < 2e-16 ***
unemp        0.017818   0.009048   1.969  0.04899 *  
wage        -0.050365   0.017924  -2.810  0.00498 ** 
distance    -0.054219   0.010498  -5.165  2.5e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1.579 on 4734 degrees of freedom
Multiple R-squared:  0.2218,    Adjusted R-squared:  0.2211 
F-statistic: 337.2 on 4 and 4734 DF,  p-value: < 2.2e-16
# vou exportar o dataset do R para Excel
writexl::write_xlsx(cd.d, "cd.d.xlsx")

Agora farei com o Python chamando o pacote reticulate.

Primeiro instale e carregue o pacote reticulate. Esta rotina estará baseada em Deepanshu Bhalla (2018): https://www.r-bloggers.com/run-python-from-r/.

# chunk em R Instalação do `reticulate`
if (!require(reticulate)) {
    install.packages("reticulate")
    library(reticulate)
}
use_python("/usr/local/bin/python")
os <- import("os")
os$listdir(".")
[1] ".RData"                 ".Rhistory"              ".Rproj.user"           
[4] "cd.d.xlsx"              "fig1.jpg"               "regressao_python.html" 
[7] "regressao_python.Rmd"   "regressao_python.Rproj" "regressao_python_cache"
# Checar a existência do Python em seu sistema
py_available()
[1] TRUE
# chunk em python
import os
import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels as st

Agora carregue os pacotes numpy e pandas:

# chunk em R
numpy <- import("numpy")
pd <- import("pandas")
st <- import("statsmodels")
sm <- import("statsmodels.api")

Faremos algumas operações com o numpy:

# chunk em R matriz 2x2
y <- array(1:4, c(2, 2))
x <- numpy$array(y)

# transposta de x
numpy$transpose(x)
     [,1] [,2]
[1,]    1    2
[2,]    3    4
# Eigenvalues e eigen vectors
numpy$linalg$eig(x)
[[1]]
[1] -0.3722813  5.3722813

[[2]]
           [,1]       [,2]
[1,] -0.9093767 -0.5657675
[2,]  0.4159736 -0.8245648
# Raiz quadrada e exponencial de x observe que nesse caso ele faz célula a célula
# da matriz
numpy$sqrt(x)
         [,1]     [,2]
[1,] 1.000000 1.732051
[2,] 1.414214 2.000000
numpy$exp(x)
         [,1]     [,2]
[1,] 2.718282 20.08554
[2,] 7.389056 54.59815

1.1 Chunk em Python no Rmd

# Load Python packages
# chunk em Python
import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels as st
# Importing Dataset
dados = pd.read_excel("cd.d.xlsx")
# Number of rows and columns
dados.shape
# selecionar as 5 primeiras linhas do dataset para checar   
(4739, 14)
dados.head(n = 5)

# Group By
   gender ethnicity      score fcollege  ...  tuition education income  region
0    male     other  39.150002      yes  ...  0.88915        12   high   other
1  female     other  48.869999       no  ...  0.88915        12    low   other
2    male     other  48.740002       no  ...  0.88915        12    low   other
3    male      afam  40.400002       no  ...  0.88915        12    low   other
4  female     other  40.480000       no  ...  0.88915        13    low   other

[5 rows x 14 columns]
dados.groupby("gender").mean()

# Filter
            score     unemp      wage  distance   tuition  education
gender                                                              
female  50.256335  7.668346  9.467362  1.810038  0.811829  13.791923
male    51.658083  7.510753  9.540795  1.794156  0.817986  13.827022
t = dados.loc[(dados.score >= 65) & (dados.education >= 17),:]

Muito bem, agora vamos rodar a regressão que fizemos pelo R (reg1), mas agora pelo Python em um chunk de R usando o reticulate.

# chunk em Python
# Load Pandas package
import pandas as pd
# Importing Dataset
dados = pd.read_excel("cd.d.xlsx")
df = pd.DataFrame(dados,columns=['score','unemp','wage','distance','education'])
X = df[['score','unemp','wage','distance']] # 4 variaveis para regressao multipla
X = sm.add_constant(X)
Y = df['education']
import statsmodels as st
import statsmodels.api as sm
mod1 = sm.OLS(df['education'], X)
res = mod1.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:              education   R-squared:                       0.222
Model:                            OLS   Adj. R-squared:                  0.221
Method:                 Least Squares   F-statistic:                     337.2
Date:                Sat, 22 Aug 2020   Prob (F-statistic):          9.94e-256
Time:                        19:02:12   Log-Likelihood:                -8886.5
No. Observations:                4739   AIC:                         1.778e+04
Df Residuals:                    4734   BIC:                         1.782e+04
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          9.3776      0.204     46.079      0.000       8.979       9.777
score          0.0957      0.003     35.949      0.000       0.090       0.101
unemp          0.0178      0.009      1.969      0.049     7.9e-05       0.036
wage          -0.0504      0.018     -2.810      0.005      -0.086      -0.015
distance      -0.0542      0.010     -5.165      0.000      -0.075      -0.034
==============================================================================
Omnibus:                      175.689   Durbin-Watson:                   1.897
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              125.836
Skew:                           0.296   Prob(JB):                     4.73e-28
Kurtosis:                       2.464   Cond. No.                         472.
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Chamarei o chunk em R mas executarei os comandos de regressão do Python para Y~X sendo que criarei as matrizes Y e X antes de rodar o modelo.

# chunk em R
numpy <- import("numpy")
pd <- import("pandas")
st <- import("statsmodels")
sm <- import("statsmodels.api")
# Fit and summarize OLS model
dados = pd$read_excel("cd.d.xlsx")
# regressores
attach(dados)
X = dados[, c("score", "unemp", "wage", "distance")]
X = sm$add_constant(X)
# regressando
Y = dados[, "education"]
mod1 = sm$OLS(Y, X)
res = mod1$fit()
print(res$summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.222
Model:                            OLS   Adj. R-squared:                  0.221
Method:                 Least Squares   F-statistic:                     337.2
Date:                Sat, 22 Aug 2020   Prob (F-statistic):          9.94e-256
Time:                        19:02:13   Log-Likelihood:                -8886.5
No. Observations:                4739   AIC:                         1.778e+04
Df Residuals:                    4734   BIC:                         1.782e+04
Df Model:                           4                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          9.3776      0.204     46.079      0.000       8.979       9.777
score          0.0957      0.003     35.949      0.000       0.090       0.101
unemp          0.0178      0.009      1.969      0.049     7.9e-05       0.036
wage          -0.0504      0.018     -2.810      0.005      -0.086      -0.015
distance      -0.0542      0.010     -5.165      0.000      -0.075      -0.034
==============================================================================
Omnibus:                      175.689   Durbin-Watson:                   1.897
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              125.836
Skew:                           0.296   Prob(JB):                     4.73e-28
Kurtosis:                       2.464   Cond. No.                         472.
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

1.2 Console Python dentro da sessão R

É possível criar um console temporário do Python dentro da sessão R. Os objetos criados no Python estarão disponíveis na sessão R assim como os objetos do R para o Python. Utilizando a função repl_python() ela ficará interativa. Ver abaixo. A função exit é para retornar e fechar o console Python.

# chunk em R
# não rodei, pois a ideia é que execute esses códigos no console digitando diretamente
repl_python()


# Load Pandas package
import pandas as pd
# Importing Dataset
dados = pd.read_excel("cd.d.xlsx")
# Number of rows and columns
dados.shape
# Select random no. of rows 
dados.sample(n = 10)

# Group By
dados.groupby("gender").mean()

# Filter
t = dados.loc[(dados.score >= 65) & (dados.education >= 17),:]

# Return to R
exit

Referências

Allaire,JJ; Ushey, Kevin; Tang, Yuan. reticulate: Interface to ‘Python’. R package version 1.11.1. 2019. Disponível em: https://CRAN.R-project.org/package=reticulate.

Bhalla, Deepanshu. Run Python from R. 2018. Disponível em: https://www.r-bloggers.com/run-python-from-r/.

Schwendinger, Florian. PythonInR: Use ‘Python’ from Within ‘R’. R package version 0.1-6. 2018. Disponível em: https://CRAN.R-project.org/package=PythonInR.

LS0tDQp0aXRsZTogIkVjb25vbWV0cmlhOiBQeXRob24gbm8gUlN0dWRpbyINCmF1dGhvcjogIkFkcmlhbm8gTWFyY29zIFJvZHJpZ3VlcyBGaWd1ZWlyZWRvLCAqZS1tYWlsOiBhZHJpYW5vLmZpZ3VlaXJlZG9AdWZtcy5icioiDQpsaW5rY29sb3I6IGJsdWUNCmFic3RyYWN0OiANCiAgVGhpcyBpcyBhbiB1bmRlcmdyYWQgc3R1ZGVudCBsZXZlbCBpbnN0cnVjdGlvbiBmb3IgY2xhc3MgdXNlLiAgDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IGRlZmF1bHQNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgZmlnX2NhcHRpb246IHRydWUNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQpncmFwaGljczogeWVzDQotLS0NCg0KYGBge3Iga25pdHJfaW5pdCwgZWNobz1GQUxTRSwgY2FjaGU9RkFMU0V9DQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShybWFya2Rvd24pDQpsaWJyYXJ5KHJtZGZvcm1hdHMpDQoNCiMjIEdsb2JhbCBvcHRpb25zDQpvcHRpb25zKG1heC5wcmludD0iMTAwIikNCm9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwNCgkgICAgICAgICAgICAgY2FjaGU9VFJVRSwNCiAgICAgICAgICAgICAgIHByb21wdD1GQUxTRSwNCiAgICAgICAgICAgICAgIHRpZHk9VFJVRSwNCiAgICAgICAgICAgICAgIGNvbW1lbnQ9TkEsDQogICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLA0KICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSkNCm9wdHNfa25pdCRzZXQod2lkdGg9MTAwKQ0KYGBgDQoNCg0KTGljZW7Dp2Egey0jTGljZW7Dp2F9DQo9PT09PT09PT09PT09PT09PT09DQoNClRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciB0aGUgQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbi1TaGFyZUFsaWtlIDQuMCBJbnRlcm5hdGlvbmFsIExpY2Vuc2UuIFRvIHZpZXcgYSBjb3B5IG9mIHRoaXMgbGljZW5zZSwgdmlzaXQgPGh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LXNhLzQuMC8+IG9yIHNlbmQgYSBsZXR0ZXIgdG8gQ3JlYXRpdmUgQ29tbW9ucywgUE8gQm94IDE4NjYsIE1vdW50YWluIFZpZXcsIENBIDk0MDQyLCBVU0EuDQoNCiFbTGljZW5zZTogQ0MgQlktU0EgNC4wXShodHRwczovL21pcnJvcnMuY3JlYXRpdmVjb21tb25zLm9yZy9wcmVzc2tpdC9idXR0b25zLzg4eDMxL3BuZy9ieS1zYS5wbmcpeyB3aWR0aD0yNSUgfQ0KDQpDaXRhw6fDo28gey0jQ2l0YcOnw6NvfQ0KPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KU3VnZXN0w6NvIGRlIGNpdGHDp8OjbzoNCkZJR1VFSVJFRE8sIEFkcmlhbm8gTWFyY29zIFJvZHJpZ3Vlcy4gUHl0aG9uIG5vIFJTdHVkaW8uIENhbXBvIEdyYW5kZS1NUyxCcmFzaWw6IFJTdHVkaW8vUnB1YnMsIDIwMjAuIERpc3BvbsOtdmVsIGVtIDxodHRwOi8vcnB1YnMuY29tL2Ftcm9maS9weXRob25fcnN0dWRpbz4uIA0KDQoNCkludHJvZHXDp8Ojbw0KPT09PT09PT09PT09PT09PT09PT09PT0NCg0KUG9yIHF1ZSB1c2FyaWEgbyBQeXRob24gbm8gUlN0dWRpbz8gICAgDQoNCkFzIHZlemVzIGFsZ3XDqW0gcHJlY2lzYXLDoSB1dGlsaXphciBvIFB5dGhvbiBlbSBhbGd1bSBwcm9jZWRpbWVudG8gZSBwcmVmaXJhIHJlYWxpesOhLWxvIHBvciBtZWlvIGRvIFJTdHVkaW8gKHBvciBleGVtcGxvIHBvciB0ZXIgbWFpb3IgZmFtaWxpYXJpZGFkZSBjb20gbyBSU3R1ZGlvIGUgbyBSTWFya2Rvd24pLiBPdSBuw6NvIHF1ZWlyYSBwb3IgYWxndW0gbW90aXZvIHVzYXIgb3V0cmFzIGZlcnJhbWVudGFzIHRpcG8gSnVweXRlciBvdSBQeUNoYXJtIG91IFNweWRlciwgY29tdW5zIGVtIGFwbGljYcOnw7VlcyBwYXJhIG8gUHl0aG9uLiAgICANCg0KRmFyZWkgdW0gdXNvIG1pc3RvIGVudHJlIFIgZSBQeXRob24gbmVzdGUgUk1hcmtkb3duLiBFc3RhIGFwbGljYcOnw6NvIHVzYSBvIGRhdGFzZXQgKHBhcnRlIGRvIHBhY290ZSBgQUVSYCwgYGRhdGEoIkNvbGxlZ2VEaXN0YW5jZSIpYCkgbyBxdWFsIGNvbnTDqW0gZGFkb3MgZGUgc2XDp8OjbyB0cmFuc3ZlcnNhbCBkYSBwZXNxdWlzYSBIaWdoIFNjaG9vbCBhbmQgQmV5b25kLCByZWFsaXphZGEgcGVsbyBEZXBhcnRhbWVudG8gZGUgRWR1Y2HDp8OjbyBlbSAxOTgwLCBjb20gYWNvbXBhbmhhbWVudG8gZW0gMTk4Ni4gQSBwZXNxdWlzYSBpbmNsdWl1IGVzdHVkYW50ZXMgZGUgYXByb3hpbWFkYW1lbnRlIDEuMTAwIGVzY29sYXMgc2VjdW5kw6FyaWFzLiBTw6NvIGRhZG9zIGNvbnRlbmRvIDQuNzM5IG9ic2VydmHDp8O1ZXMgZW0gMTQgdmFyacOhdmVpcyAtIGRldGFsaGVzIGVtIDxodHRwczovL3JkcnIuaW8vY3Jhbi9BRVIvbWFuL0NvbGxlZ2VEaXN0YW5jZS5odG1sPi4gUG9ydGFudG8sIGluaWNpbyBvYnRlbmRvIG9zIGRhZG9zIHBlbG8gUiwgZmFyZWkgdW0gbW9kZWxvIHBhcmEgY29tcGFyYcOnw6NvIGNvbSBhIGZ1bsOnw6NvIGBsbWAgZSBkZXBvaXMgZmFyZWkgbyBtZXNtbyBwZWxvIFB5dGhvbi4NCg0KYGBge3IgZmlnMSwgZmlnLmFsaWduID0gImNlbnRlciIsIG91dC53aWR0aCA9ICI4MCUiLCBmaWcuY2FwID0gJ0RldGFsaGFtZW50byBkbyBkYXRhc2V0IGBDb2xsZWdlRGlzdGFuY2VgIGRvIHBhY290ZSBBRVIuIEZvbnRlOiBodHRwczovL3JkcnIuaW8vY3Jhbi9BRVIvbWFuL0NvbGxlZ2VEaXN0YW5jZS5odG1sJyxlY2hvPUZBTFNFfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImZpZzEuanBnIikNCmBgYA0KDQpgYGB7cn0NCiMgdmVyIGVtIA0Kc3VwcHJlc3NNZXNzYWdlcyhzdXBwcmVzc1dhcm5pbmdzKGxpYnJhcnkoQUVSKSkpDQpkYXRhKCJDb2xsZWdlRGlzdGFuY2UiKQ0KY2QuZDwtQ29sbGVnZURpc3RhbmNlDQpyZWcxPC0gbG0oZWR1Y2F0aW9uIH4gc2NvcmUgKyB1bmVtcCArd2FnZSsgZGlzdGFuY2UsDQogICAgICAgICAgICAgICAgICBkYXRhPWNkLmQpDQpzdW1tYXJ5KHJlZzEpDQojIHZvdSBleHBvcnRhciBvIGRhdGFzZXQgZG8gUiBwYXJhIEV4Y2VsDQp3cml0ZXhsOjp3cml0ZV94bHN4KGNkLmQsICJjZC5kLnhsc3giKQ0KYGBgDQpBZ29yYSBmYXJlaSBjb20gbyBQeXRob24gY2hhbWFuZG8gbyBwYWNvdGUgYHJldGljdWxhdGVgLg0KDQpQcmltZWlybyBpbnN0YWxlIGUgY2FycmVndWUgbyBwYWNvdGUgYHJldGljdWxhdGVgLiBFc3RhIHJvdGluYSBlc3RhcsOhIGJhc2VhZGEgZW0gRGVlcGFuc2h1IEJoYWxsYSAoMjAxOCk6IDxodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS9ydW4tcHl0aG9uLWZyb20tci8+Lg0KDQpgYGB7ciBzZXR1cH0NCiMgY2h1bmsgZW0gUg0KIyBJbnN0YWxhw6fDo28gZG8gYHJldGljdWxhdGVgDQppZiAoIXJlcXVpcmUocmV0aWN1bGF0ZSkpIHsgDQogIGluc3RhbGwucGFja2FnZXMoJ3JldGljdWxhdGUnKSANCiAgbGlicmFyeShyZXRpY3VsYXRlKQ0KfQ0KdXNlX3B5dGhvbigiL3Vzci9sb2NhbC9iaW4vcHl0aG9uIikNCm9zIDwtIGltcG9ydCgib3MiKQ0Kb3MkbGlzdGRpcigiLiIpDQojIENoZWNhciBhIGV4aXN0w6puY2lhIGRvIFB5dGhvbiBlbSBzZXUgc2lzdGVtYQ0KcHlfYXZhaWxhYmxlKCkNCg0KYGBgDQoNCmBgYHtweXRob259DQojIGNodW5rIGVtIHB5dGhvbg0KaW1wb3J0IG9zDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCBzdGF0c21vZGVscy5hcGkgYXMgc20NCmltcG9ydCBzdGF0c21vZGVscyBhcyBzdA0KYGBgDQoNCkFnb3JhIGNhcnJlZ3VlIG9zIHBhY290ZXMgYG51bXB5YCBlIGBwYW5kYXNgOg0KDQpgYGB7cn0NCiNjaHVuayBlbSBSDQpudW1weSA8LSBpbXBvcnQoIm51bXB5IikNCnBkIDwtIGltcG9ydCgicGFuZGFzIikNCnN0IDwtIGltcG9ydCgic3RhdHNtb2RlbHMiKQ0Kc208LSBpbXBvcnQoInN0YXRzbW9kZWxzLmFwaSIpDQpgYGANCg0KRmFyZW1vcyBhbGd1bWFzIG9wZXJhw6fDtWVzIGNvbSBvIGBudW1weWA6DQoNCmBgYHtyfQ0KIyBjaHVuayBlbSBSDQojIG1hdHJpeiAyeDINCnkgPC0gYXJyYXkoMTo0LCBjKDIsIDIpKQ0KeCA8LSBudW1weSRhcnJheSh5KQ0KDQojIHRyYW5zcG9zdGEgZGUgeA0KbnVtcHkkdHJhbnNwb3NlKHgpDQoNCiMgRWlnZW52YWx1ZXMgZSBlaWdlbiB2ZWN0b3JzDQpudW1weSRsaW5hbGckZWlnKHgpDQoNCiMgUmFpeiBxdWFkcmFkYSBlIGV4cG9uZW5jaWFsIGRlIHgNCiMgb2JzZXJ2ZSBxdWUgbmVzc2UgY2FzbyBlbGUgZmF6IGPDqWx1bGEgYSBjw6lsdWxhIGRhIG1hdHJpeg0KbnVtcHkkc3FydCh4KQ0KbnVtcHkkZXhwKHgpDQpgYGANCg0KDQoNCiMjIENodW5rIGVtIFB5dGhvbiBubyBSbWQNCg0KYGBge3B5dGhvbn0NCiMgTG9hZCBQeXRob24gcGFja2FnZXMNCiMgY2h1bmsgZW0gUHl0aG9uDQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCBzdGF0c21vZGVscy5hcGkgYXMgc20NCmltcG9ydCBzdGF0c21vZGVscyBhcyBzdA0KIyBJbXBvcnRpbmcgRGF0YXNldA0KZGFkb3MgPSBwZC5yZWFkX2V4Y2VsKCJjZC5kLnhsc3giKQ0KIyBOdW1iZXIgb2Ygcm93cyBhbmQgY29sdW1ucw0KZGFkb3Muc2hhcGUNCiMgc2VsZWNpb25hciBhcyA1IHByaW1laXJhcyBsaW5oYXMgZG8gZGF0YXNldCBwYXJhIGNoZWNhcgkNCmRhZG9zLmhlYWQobiA9IDUpDQoNCiMgR3JvdXAgQnkNCmRhZG9zLmdyb3VwYnkoImdlbmRlciIpLm1lYW4oKQ0KDQojIEZpbHRlcg0KdCA9IGRhZG9zLmxvY1soZGFkb3Muc2NvcmUgPj0gNjUpICYgKGRhZG9zLmVkdWNhdGlvbiA+PSAxNyksOl0NCmBgYA0KTXVpdG8gYmVtLCBhZ29yYSB2YW1vcyByb2RhciBhIHJlZ3Jlc3PDo28gcXVlIGZpemVtb3MgcGVsbyBSIChgcmVnMWApLCBtYXMgYWdvcmEgcGVsbyBQeXRob24gZW0gdW0gY2h1bmsgZGUgUiB1c2FuZG8gbyByZXRpY3VsYXRlLg0KDQoNCg0KYGBge3B5dGhvbn0NCiMgY2h1bmsgZW0gUHl0aG9uDQojIExvYWQgUGFuZGFzIHBhY2thZ2UNCmltcG9ydCBwYW5kYXMgYXMgcGQNCiMgSW1wb3J0aW5nIERhdGFzZXQNCmRhZG9zID0gcGQucmVhZF9leGNlbCgiY2QuZC54bHN4IikNCmRmID0gcGQuRGF0YUZyYW1lKGRhZG9zLGNvbHVtbnM9WydzY29yZScsJ3VuZW1wJywnd2FnZScsJ2Rpc3RhbmNlJywnZWR1Y2F0aW9uJ10pDQpYID0gZGZbWydzY29yZScsJ3VuZW1wJywnd2FnZScsJ2Rpc3RhbmNlJ11dICMgNCB2YXJpYXZlaXMgcGFyYSByZWdyZXNzYW8gbXVsdGlwbGENClggPSBzbS5hZGRfY29uc3RhbnQoWCkNClkgPSBkZlsnZWR1Y2F0aW9uJ10NCmltcG9ydCBzdGF0c21vZGVscyBhcyBzdA0KaW1wb3J0IHN0YXRzbW9kZWxzLmFwaSBhcyBzbQ0KbW9kMSA9IHNtLk9MUyhkZlsnZWR1Y2F0aW9uJ10sIFgpDQpyZXMgPSBtb2QxLmZpdCgpDQpwcmludChyZXMuc3VtbWFyeSgpKQ0KYGBgDQpDaGFtYXJlaSBvIGNodW5rIGVtIFIgbWFzIGV4ZWN1dGFyZWkgb3MgY29tYW5kb3MgZGUgcmVncmVzc8OjbyBkbyBQeXRob24gcGFyYSBZflggc2VuZG8gcXVlIGNyaWFyZWkgYXMgbWF0cml6ZXMgWSBlIFggYW50ZXMgZGUgcm9kYXIgbyBtb2RlbG8uIA0KDQpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQojIGNodW5rIGVtIFINCm51bXB5IDwtIGltcG9ydCgibnVtcHkiKQ0KcGQgPC0gaW1wb3J0KCJwYW5kYXMiKQ0Kc3QgPC0gaW1wb3J0KCJzdGF0c21vZGVscyIpDQpzbTwtIGltcG9ydCgic3RhdHNtb2RlbHMuYXBpIikNCiMgRml0IGFuZCBzdW1tYXJpemUgT0xTIG1vZGVsDQpkYWRvcyA9IHBkJHJlYWRfZXhjZWwoImNkLmQueGxzeCIpDQojIHJlZ3Jlc3NvcmVzDQphdHRhY2goZGFkb3MpDQpYPWRhZG9zWyxjKCdzY29yZScsJ3VuZW1wJywnd2FnZScsJ2Rpc3RhbmNlJyldDQpYID0gc20kYWRkX2NvbnN0YW50KFgpDQojIHJlZ3Jlc3NhbmRvDQpZID0gZGFkb3NbLCdlZHVjYXRpb24nXQ0KbW9kMSA9IHNtJE9MUyhZLCBYKQ0KcmVzID0gbW9kMSRmaXQoKQ0KcHJpbnQocmVzJHN1bW1hcnkoKSkNCmBgYA0KDQoNCiMjIENvbnNvbGUgUHl0aG9uIGRlbnRybyBkYSBzZXNzw6NvIFINCg0Kw4kgcG9zc8OtdmVsIGNyaWFyIHVtIGNvbnNvbGUgdGVtcG9yw6FyaW8gZG8gUHl0aG9uIGRlbnRybyBkYSBzZXNzw6NvIFIuIE9zIG9iamV0b3MgY3JpYWRvcyBubyBQeXRob24gZXN0YXLDo28gZGlzcG9uw612ZWlzIG5hIHNlc3PDo28gUiBhc3NpbSBjb21vIG9zIG9iamV0b3MgZG8gUiBwYXJhIG8gUHl0aG9uLg0KVXRpbGl6YW5kbyBhIGZ1bsOnw6NvIGByZXBsX3B5dGhvbigpYCBlbGEgZmljYXLDoSBpbnRlcmF0aXZhLiBWZXIgYWJhaXhvLiBBIGZ1bsOnw6NvIGBleGl0YCDDqSBwYXJhIHJldG9ybmFyIGUgZmVjaGFyIG8gY29uc29sZSBQeXRob24uIA0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KIyBjaHVuayBlbSBSDQojIG7Do28gcm9kZWksIHBvaXMgYSBpZGVpYSDDqSBxdWUgZXhlY3V0ZSBlc3NlcyBjw7NkaWdvcyBubyBjb25zb2xlIGRpZ2l0YW5kbyBkaXJldGFtZW50ZQ0KcmVwbF9weXRob24oKQ0KDQoNCiMgTG9hZCBQYW5kYXMgcGFja2FnZQ0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KIyBJbXBvcnRpbmcgRGF0YXNldA0KZGFkb3MgPSBwZC5yZWFkX2V4Y2VsKCJjZC5kLnhsc3giKQ0KIyBOdW1iZXIgb2Ygcm93cyBhbmQgY29sdW1ucw0KZGFkb3Muc2hhcGUNCiMgU2VsZWN0IHJhbmRvbSBuby4gb2Ygcm93cwkNCmRhZG9zLnNhbXBsZShuID0gMTApDQoNCiMgR3JvdXAgQnkNCmRhZG9zLmdyb3VwYnkoImdlbmRlciIpLm1lYW4oKQ0KDQojIEZpbHRlcg0KdCA9IGRhZG9zLmxvY1soZGFkb3Muc2NvcmUgPj0gNjUpICYgKGRhZG9zLmVkdWNhdGlvbiA+PSAxNyksOl0NCg0KIyBSZXR1cm4gdG8gUg0KZXhpdA0KYGBgDQoNCg0KUmVmZXLDqm5jaWFzIHstI1JlZmVyw6puY2lhc30NCj09PT09PT09PT09PT09PT09PT09PT09DQoNCkFsbGFpcmUsSko7IFVzaGV5LCBLZXZpbjsgVGFuZywgWXVhbi4gcmV0aWN1bGF0ZTogSW50ZXJmYWNlIHRvICdQeXRob24nLiBSIHBhY2thZ2UgdmVyc2lvbiAxLjExLjEuIDIwMTkuIERpc3BvbsOtdmVsIGVtOiA8aHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1yZXRpY3VsYXRlPi4NCiAgDQpCaGFsbGEsIERlZXBhbnNodS4gUnVuIFB5dGhvbiBmcm9tIFIuIDIwMTguIERpc3BvbsOtdmVsIGVtOiA8aHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vcnVuLXB5dGhvbi1mcm9tLXIvPi4NCg0KU2Nod2VuZGluZ2VyLCAgIEZsb3JpYW4uIFB5dGhvbkluUjogVXNlICdQeXRob24nIGZyb20gV2l0aGluICdSJy4gUiBwYWNrYWdlIHZlcnNpb24gMC4xLTYuIDIwMTguIERpc3BvbsOtdmVsIGVtOiA8aHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1QeXRob25JblI+Lg0K