Ir al contenido
σestadistica.ar

estadistica.ar · Enciclopedia de Estadística en españolhttps://estadistica.ar/conceptos/estadistica-en-r-y-python

M16 · CómputoIntermedioProcedimiento

Estadística en R y Python: dónde está cada análisis y qué hace por defecto

La función de cada análisis en R y en Python, y los valores por defecto que hacen que el mismo análisis dé resultados distintos en uno y otro.

Por Jorge Luis Montivero, Licenciado en Estadística · Mu3Sigma

1 · Para qué es esta página

Para empezar a usar R o Python están primeros pasos en R y en Python. Esta página es para después, cuando ya se usa uno de los dos y hay que encontrar una función, traducir un análisis al otro lenguaje o entender por qué dos personas que hicieron «lo mismo» obtuvieron números distintos.

Casi siempre la respuesta a eso último es la misma: los dos programas calculan bien, pero por defecto no calculan lo mismo. Cada función tiene opciones que el que no las toca no ve, y en R y en Python esas opciones vienen puestas distinto.

El script que hace la comparación es datos/computo/preparar-r-y-python.py: corre el lado de R con Rscript, calcula lo mismo en Python y se detiene si algo que tiene que coincidir no coincide. Los resultados, con las versiones usadas, quedan en datos/computo/r-y-python.json.

2 · Dónde está cada análisis

En Python se usan cuatro librerías: pandas para la tabla de datos, SciPy (scipy.stats) para las pruebas, statsmodels para los modelos y scikit-learn para el aprendizaje automático. En R casi todo viene en la instalación base.

AnálisisRPython
Media, desvíomean(x), sd(x)df.x.mean(), df.x.std()
Cuantilesquantile(x, 0.25)df.x.quantile(0.25)
Tabla de frecuenciastable(x)df.x.value_counts()
Tabla de contingenciatable(x, y)pd.crosstab(df.x, df.y)
Prueba t, dos muestrast.test(y ~ g)stats.ttest_ind(a, b)
Prueba t pareadat.test(a, b, paired = TRUE)stats.ttest_rel(a, b)
Chi-cuadradochisq.test(tabla)stats.chi2_contingency(tabla)
Exacta de Fisherfisher.test(tabla)stats.fisher_exact(tabla)
Intervalo para una proporciónprop.test(k, n), binom.test(k, n)proportion_confint(k, n)
Mann-Whitneywilcox.test(a, b)stats.mannwhitneyu(a, b)
Correlacióncor.test(x, y)stats.pearsonr(x, y), stats.spearmanr(x, y)
Regresión lineallm(y ~ x)smf.ols("y ~ x", df).fit()
ANOVAanova(lm(y ~ g))sm.stats.anova_lm(modelo)
Regresión logísticaglm(y ~ x, family = binomial)smf.logit("y ~ x", df).fit()
Normalidadshapiro.test(x)stats.shapiro(x)

En la tabla, stats es scipy.stats, smf es statsmodels.formula.api, sm es statsmodels.api y proportion_confint viene de statsmodels.stats.proportion.

3 · Los valores por defecto que cambian el resultado

Ordenados de mayor a menor daño. Los primeros cambian la conclusión; los últimos, un decimal.

La regresión sin ordenada al origen

lm(y ~ x) en R y smf.ols("y ~ x", df) en Python incluyen la ordenada al origen sin que nadie la pida. sm.OLS(y, x), la otra forma de statsmodels, no la incluye: ajusta una recta obligada a pasar por cero, salvo que se agregue una columna de unos con sm.add_constant(x).

Ingreso de la ocupación principal según edad, 594 ocupados:

PendienteR2R^2
R, lm(P21 ~ CH06)$9.817 por año0,017
Python, smf.ols("P21 ~ CH06", df)$9.817 por año0,017
Python, sm.OLS(df.P21, df.CH06)$20.443 por año0,468

Welch o Student en la prueba t

t.test de R hace Welch por defecto: no supone varianzas iguales. scipy.stats.ttest_ind hace Student por defecto: las supone iguales (equal_var=True).

Ingreso de patrones contra asalariados. Son 41 patrones con un desvío de $2,23 millones contra 444 asalariados con un desvío de $0,77 millones:

ttglpp
R por defecto (Welch)1,9140,90,064
SciPy por defecto (Student)4,194830,00003

Para obtener uno u otro en cualquiera de los dos: t.test(..., var.equal = TRUE) en R da Student, y ttest_ind(..., equal_var=False) en SciPy da Welch. Welch es la opción más segura cuando no hay buenas razones para suponer varianzas iguales.

La logística que se encoge sola

glm en R y smf.logit en statsmodels estiman la regresión logística por máxima verosimilitud. LogisticRegression() de scikit-learn aplica por defecto una penalización (L2, con C=1), que achica los coeficientes hacia cero. Es una regresión penalizada aunque nadie lo haya pedido.

Probabilidad de estar desocupado según edad, sexo y secundario completo, 655 activos con 26 desocupados:

CoeficienteR y statsmodelsscikit-learn por defecto
Ordenada−1,038−1,140
Edad−0,036−0,035
Mujer−0,284−0,261
Secundario completo−0,962−0,825

El efecto del secundario aparece un 14 % más chico, y el odds ratio pasa de 0,38 a 0,44. Con LogisticRegression(penalty=None) scikit-learn coincide con R. La regla práctica: para interpretar coeficientes, statsmodels; para predecir, scikit-learn, sabiendo qué hace.

El ANOVA de dos factores depende del orden

anova(lm(...)) en R y anova_lm(...) en statsmodels usan sumas de cuadrados de tipo I (secuenciales): cada factor se evalúa después de los que se escribieron antes. Con grupos de distinto tamaño, el orden cambia el resultado.

Ingreso según sexo y nivel educativo (tres niveles), con celdas de entre 19 y 175 casos:

ModeloFF del sexo
ingreso ~ sexo + nivel0,14
ingreso ~ nivel + sexo2,73

Las dos salidas son correctas: responden preguntas distintas. La primera pregunta si el sexo importa sin tener en cuenta el nivel educativo. La segunda, si importa a igual nivel. R y Python coinciden entre sí; el que da distinto es SPSS, que usa por defecto el tipo III (2,73 en este caso, igual que poner el sexo al final). En R se obtiene con car::Anova(modelo, type = 3) y en Python con anova_lm(modelo, typ=3), en los dos casos con contrastes de suma a cero. Ver ANOVA de dos factores.

El intervalo para una proporción

Cada función usa un método distinto, y ninguna lo dice en el nombre. Seis desocupados entre 62 jóvenes activos (9,7 %):

FunciónMétodoIntervalo del 95 %
proportion_confint(6, 62)Wald (normal)2,3 % a 17,0 %
proportion_confint(6, 62, method="wilson")Wilson4,5 % a 19,5 %
prop.test(6, 62)Wilson con corrección por continuidad4,0 % a 20,5 %
binom.test(6, 62)Clopper-Pearson (exacto)3,6 % a 19,9 %

El de statsmodels por defecto es el de Wald, el de los libros, y es el que peor funciona con proporciones chicas: queda corrido hacia abajo y cubre menos de lo que promete. Ver intervalo para una proporción.

Los valores perdidos

En la EPH, el ingreso que no se declaró viene como −9. Pasado a perdido, en los ocupados de Catamarca hay 30 de 631 (4,8 %):

Media del ingreso
R, mean(x)NA
NumPy, np.mean(x)nan
pandas, df.x.mean()$871.719

R y NumPy se niegan a calcular y obligan a decidir (na.rm = TRUE, np.nanmean). pandas excluye los perdidos sin avisar, y lo mismo hacen std, corr y casi todo lo demás. Es cómodo, pero la no respuesta pasa sin que nadie la note, sea del 5 % como acá o del 30 %. Ver datos faltantes.

El desvío: n o n − 1

sd() en R y .std() en pandas dividen por n−1n-1, el desvío muestral. np.std() de NumPy divide por nn salvo que se le pase ddof=1. Sobre las edades de los primeros 12 ocupados del archivo:

Desvío
R, pandas, np.std(x, ddof=1)14,62 años
np.std(x)13,996 años

Con muestras grandes la diferencia desaparece; con 12 casos es de 4 %.

Los cuartiles

R, pandas, NumPy y Excel usan por defecto el mismo método (el tipo 7 de la clasificación de Hyndman y Fan). SPSS usa otro (el tipo 6). Sobre las mismas 12 edades, el primer cuartil da 30,75 en los primeros y 30,25 en SPSS. Para reproducir a SPSS: quantile(x, 0.25, type = 6) en R y np.percentile(x, 25, method="weibull") en NumPy. Ver cuantiles.

El odds ratio de la prueba de Fisher

fisher.test de R informa el odds ratio condicional (un estimador de máxima verosimilitud condicional). scipy.stats.fisher_exact informa el odds ratio muestral, el de la cuenta a mano. En una tabla de desocupación por sexo entre los jóvenes activos (33, 3, 23, 3), R da 1,426 y SciPy 1,435. El valor pp es idéntico. Para obtener el condicional en Python está odds_ratio(tabla), del módulo scipy.stats.contingency. Con tablas grandes los dos se acercan; con tablas chicas pueden alejarse bastante. Ver prueba exacta de Fisher.

Las que coinciden, aunque no se espere

Dos casos en los que R y Python sí hacen lo mismo por defecto, y lo que difiere es la cuenta del libro:

  • Chi-cuadrado en tablas de 2×2. chisq.test y chi2_contingency aplican la corrección de Yates. Sexo por secundario completo da 13,81 con corrección y 14,55 sin ella (correct = FALSE, correction=False). Quien lo calcula a mano obtiene el segundo y no entiende por qué el programa no coincide.
  • Redondeo de un 5 exacto. round(2.5) da 2 en R y en Python, porque los dos redondean al par. Excel, con REDONDEAR, da 3. Una tabla armada con un programa y verificada con el otro puede diferir en el último dígito sin que haya ningún error. Ver redondeo.

4 · Cómo saber qué hizo una función

  1. Leer la firma, no el ejemplo. help(t.test) en R o help(stats.ttest_ind) en Python muestran cada argumento con su valor por defecto. var.equal = FALSE y equal_var=True están ahí, a la vista.
  2. Mirar lo que la salida informa. R escribe «Welch Two Sample t-test» en el título; si la salida dice «gl = 40,9», los grados de libertad no enteros delatan a Welch. Ver cómo leer la salida.
  3. Probar con un caso conocido. Antes de confiar en una función nueva, correrla sobre un ejemplo del libro o de este sitio cuyo resultado se sabe.
  4. Escribir la opción aunque sea la por defecto. t.test(..., var.equal = FALSE) dice lo mismo que t.test(...), pero quien lea el código, o quien lo traduzca al otro lenguaje, ya no tiene que adivinar.

Los análisis de la sección anterior, escritos así, dan lo mismo en los dos lenguajes:

t.test(P21 ~ grupo, data = d, var.equal = FALSE)        # Welch
lm(P21 ~ CH06, data = d)                                # con ordenada al origen
glm(desoc ~ CH06 + mujer + sec, data = d, family = binomial)
quantile(d$CH06, 0.25, type = 7)
mean(d$P21, na.rm = TRUE)                               # decidido, no por omisión
stats.ttest_ind(a, b, equal_var=False)                  # Welch
smf.ols("P21 ~ CH06", data=d).fit()                     # con ordenada al origen
smf.logit("desoc ~ CH06 + mujer + sec", data=d).fit()   # sin penalización
d.CH06.quantile(0.25, interpolation="linear")
d.P21.mean(skipna=True)                                 # decidido, no por omisión

5 · Errores frecuentes

  • Usar sm.OLS(y, x) sin add_constant. La recta pasa por cero y el R2R^2 sale inflado: 0,47 donde corresponde 0,02.
  • Traducir t.test a ttest_ind sin equal_var=False. Se pasa de Welch a Student sin darse cuenta. Con grupos desparejos, la conclusión puede cambiar.
  • Interpretar los coeficientes de LogisticRegression() como los de una logística. Por defecto están penalizados y achicados.
  • Comparar un ANOVA de R o Python con uno de SPSS. Tipo I contra tipo III: con diseños no balanceados, las sumas de cuadrados no tienen por qué coincidir.
  • Confiar en que pandas avise de los perdidos. No avisa: los saca. Contar con df.x.isna().sum() antes de describir.
  • Usar proportion_confint sin method=. Devuelve Wald, que falla justamente con proporciones chicas y muestras chicas.
  • Pensar que un resultado distinto es un error. Antes de buscar el error, comparar los valores por defecto: casi siempre la diferencia está ahí.

Seguir leyendo

Cómo citar: Montivero, J. L. (2026). Estadística en R y Python: dónde está cada análisis y qué hace por defecto. estadistica.ar. https://estadistica.ar/conceptos/estadistica-en-r-y-python

estadistica.ar es gratuita y sin publicidad. Si te sirvió, podés invitar un cafecito por transferencia al alias mu3sigma. Asesoramiento estadístico, encuestas y capacitaciones: m3s.pro · WhatsApp +54 9 383 499-7929.

Cómo citar esta entrada

APA (7.ª edición)

Montivero, J. L. (2026). Estadística en R y Python: dónde está cada análisis y qué hace por defecto. estadistica.ar. https://estadistica.ar/conceptos/estadistica-en-r-y-python

BibTeX

@misc{estadistica_ar_estadistica_en_r_y_python,
  author       = {Montivero, Jorge Luis},
  title        = {{Estadística en R y Python: dónde está cada análisis y qué hace por defecto}},
  year         = {2026},
  howpublished = {\url{https://estadistica.ar/conceptos/estadistica-en-r-y-python}},
  note         = {estadistica.ar. Consultado el \today},
  urldate      = {2026-10-01}
}

Última actualización: 1 de octubre de 2026. El contenido está bajo licencia CC BY-NC-SA 4.0: se puede reutilizar citando la fuente y sin fines comerciales.