¿Qué tipo de curva (o modelo) debo ajustar a mis datos porcentuales?

15

Estoy tratando de crear una figura que muestre la relación entre las copias virales y la cobertura del genoma (CCG). Así es como se ven mis datos:

Carga viral vs GCC

Al principio, acabo de trazar una regresión lineal, pero mis supervisores me dijeron que eso era incorrecto y que probé una curva sigmoidea. Así que hice esto usando geom_smooth:

library(scales)
ggplot(scatter_plot_new, aes(x = Copies_per_uL, y = Genome_cov, colour = Virus)) +
    geom_point() +
    scale_x_continuous(trans = log10_trans(), breaks = trans_breaks("log10", function(x) 10^x), labels = trans_format("log10", math_format(10^.x))) +
        geom_smooth(method = "gam", formula = y ~ s(x), se = FALSE, size = 1) +
    theme_bw() +
    theme(legend.position = 'top', legend.text = element_text(size = 10), legend.title = element_text(size = 12), axis.text = element_text(size = 10), axis.title = element_text(size=12), axis.title.y = element_text(margin = margin (r = 10)), axis.title.x = element_text(margin = margin(t = 10))) +
    labs(x = "Virus copies/µL", y = "GCC (%)") +
    scale_y_continuous(breaks=c(25,50,75,100))

Carga viral vs GCC - geom_smooth

Sin embargo, mis supervisores dicen que esto también es incorrecto porque las curvas hacen que parezca que GCC puede superar el 100%, lo que no puede.

Mi pregunta es: ¿cuál es la mejor manera de mostrar la relación entre las copias de virus y el CCG? Quiero dejar en claro que A) copias bajas de virus = bajo CCG, y que B) después de una cierta cantidad de virus copia las mesetas de CCG.

He investigado muchos métodos diferentes: GAM, LOESS, logístico, por partes, pero no sé cómo saber cuál es el mejor método para mis datos.

EDITAR: estos son los datos:

>print(scatter_plot_new)  
Subsample   Virus   Genome_cov  Copies_per_uL
1   S1.1_RRAV   RRAV    100 92500
2   S1.2_RRAV   RRAV    100 95900
3   S1.3_RRAV   RRAV    100 92900
4   S2.1_RRAV   RRAV    100 4049.54
5   S2.2_RRAV   RRAV    96.9935 3809
6   S2.3_RRAV   RRAV    94.5054 3695.06
7   S3.1_RRAV   RRAV    3.7235  86.37
8   S3.2_RRAV   RRAV    11.8186 84.2
9   S3.3_RRAV   RRAV    11.0929 95.2
10  S4.1_RRAV   RRAV    0   2.12
11  S4.2_RRAV   RRAV    5.0799  2.71
12  S4.3_RRAV   RRAV    0   2.39
13  S5.1_RRAV   RRAV    4.9503  0.16
14  S5.2_RRAV   RRAV    0   0.08
15  S5.3_RRAV   RRAV    4.4147  0.08
16  S1.1_UMAV   UMAV    5.7666  1.38
17  S1.2_UMAV   UMAV    26.0379 1.72
18  S1.3_UMAV   UMAV    7.4128  2.52
19  S2.1_UMAV   UMAV    21.172  31.06
20  S2.2_UMAV   UMAV    16.1663 29.87
21  S2.3_UMAV   UMAV    9.121   32.82
22  S3.1_UMAV   UMAV    92.903  627.24
23  S3.2_UMAV   UMAV    83.0314 615.36
24  S3.3_UMAV   UMAV    90.3458 632.67
25  S4.1_UMAV   UMAV    98.6696 11180
26  S4.2_UMAV   UMAV    98.8405 12720
27  S4.3_UMAV   UMAV    98.7939 8680
28  S5.1_UMAV   UMAV    98.6489 318200
29  S5.2_UMAV   UMAV    99.1303 346100
30  S5.3_UMAV   UMAV    98.8767 345100
Teaelleceecee
fuente
66
Parece que una regresión logística sería lo mejor, ya que está limitada entre 0 y 100%.
mkt - Restablecer Mónica
1
Pruebe el modelo (lineal) por piezas (2).
user158565
3
intente agregar method.args=list(family=quasibinomial))los argumentos geom_smooth()en su código ggplot original.
Ben Bolker
44
PD: te animo a que no suprimas los errores estándar con se=FALSE. Siempre es bueno mostrarle a la gente cuán grande es la incertidumbre ...
Ben Bolker
2
No tiene suficientes puntos de datos en la región de transición para afirmar con ninguna autoridad que hay una curva suave. Podría ajustar fácilmente una función Heaviside a los puntos que nos está mostrando.
Carl Witthoft

Respuestas:

6

Otra forma de hacerlo sería utilizar una formulación bayesiana, puede ser un poco pesado para empezar, pero tiende a hacer que sea mucho más fácil expresar detalles de su problema, así como obtener mejores ideas sobre dónde está la "incertidumbre". es

Stan es una muestra de Monte Carlo con una interfaz programática relativamente fácil de usar, las bibliotecas están disponibles para R y otros, pero estoy usando Python aquí

Usamos un sigmoide como todos los demás: tiene motivaciones bioquímicas además de ser matemáticamente muy conveniente para trabajar. Una buena parametrización para esta tarea es:

import numpy as np

def sigfn(x, alpha, beta):
    return 1 / (1 + np.exp(-(x - alpha) * beta))

donde alphadefine el punto medio de la curva sigmoidea (es decir, donde cruza el 50%) y betadefine la pendiente, los valores más cercanos a cero son más planos

para mostrar cómo se ve esto, podemos obtener sus datos y trazarlos con:

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_table('raw_data.txt', delim_whitespace=True)
df.columns = ['subsample', 'virus', 'coverage', 'copies']
df.coverage /= 100

x = np.logspace(-1, 6, 201)
plt.semilogx(x, sigfn(np.log(x), 5.5, 3), label='sigfn', color='C2')

sns.scatterplot(df.copies, df.coverage, hue=df.virus, edgecolor='none')

donde raw_data.txtcontiene los datos que proporcionó y transformé la cobertura en algo más útil. los coeficientes 5.5 y 3 se ven bien y dan una gráfica muy parecida a las otras respuestas:

Datos de la trama y ajuste manual

para "ajustar" esta función usando Stan necesitamos definir nuestro modelo usando su propio lenguaje que es una mezcla entre R y C ++. un modelo simple sería algo así como:

data {
    int<lower=1> N;  // number of rows
    vector[N] log_copies;
    vector<lower=0,upper=1>[N] coverage;
}
parameters {
    real alpha;
    real beta;
    real<lower=0> sigma;
}
model {
    vector[N] mu;
    mu = 1 ./ (1 + exp(-(log_copies - alpha) * beta));

    sigma ~ cauchy(0, 0.1);
    alpha ~ normal(0, 5);
    beta ~ normal(0, 5);

    coverage ~ normal(mu, sigma);
}

que con suerte dice OK. Tenemos un databloque que define los datos que esperamos cuando muestreamos el modelo, parametersdefinimos las cosas que se muestrean y modeldefine la función de probabilidad. Le dice a Stan que "compile" el modelo, lo que lleva un tiempo, y luego puede tomar muestras de él con algunos datos. por ejemplo:

import pystan

model = pystan.StanModel(model_code=code)
model.sampling(data=dict(
    N=len(df),
    log_copies=np.log(df.copies),
    coverage=df.coverage,
), iter=10000, chains=4, thin=10)

import arviz
arviz.plot_trace(fit)

arviz hace que las gráficas de diagnóstico sean fáciles, mientras que la impresión del ajuste le brinda un buen resumen de parámetros de estilo R:

4 chains, each with iter=10000; warmup=5000; thin=10; 
post-warmup draws per chain=500, total post-warmup draws=2000.

        mean se_mean     sd   2.5%    25%    50%    75%  97.5%  n_eff   Rhat
alpha   5.51  6.0e-3   0.26   4.96   5.36   5.49   5.64   6.12   1849    1.0
beta    2.89    0.04   1.71   1.55   1.98   2.32   2.95   8.08   1698    1.0
sigma   0.08  2.7e-4   0.01   0.06   0.07   0.08   0.09    0.1   1790    1.0
lp__   57.12    0.04   1.76   52.9   56.1  57.58  58.51  59.19   1647    1.0

La gran desviación estándar betaindica que los datos realmente no proporcionan mucha información sobre este parámetro. También algunas de las respuestas que dan más de 10 dígitos significativos en sus ajustes de modelo exageran un poco las cosas

Debido a que algunas respuestas señalaron que cada virus podría necesitar sus propios parámetros, extendí el modelo para permitir alphay betavariar según el "Virus". todo se vuelve un poco complicado, pero los dos virus casi con seguridad tienen alphavalores diferentes (es decir, necesita más copias / μL de RRAV para la misma cobertura) y un gráfico que muestra esto es:

trama de datos y muestras de MC

los datos son los mismos que antes, pero dibujé una curva para 40 muestras de la parte posterior. UMAVparece relativamente bien determinado, mientras que RRAVpodría seguir la misma pendiente y necesitar un mayor conteo de copias, o tener una pendiente más pronunciada y un conteo de copias similar. la mayor parte de la masa posterior depende de la necesidad de un mayor número de copias, pero esta incertidumbre podría explicar algunas de las diferencias en otras respuestas para encontrar cosas diferentes

Sobre todo solía responder esto como un ejercicio para mejorar mi conocimiento de Stan, y he puesto un cuaderno Jupyter de esto aquí en caso de que alguien esté interesado / quiera replicar esto.

Sam Mason
fuente
14

(Editado teniendo en cuenta los comentarios a continuación. Gracias a @BenBolker y @WeiwenNg por sus útiles comentarios).

Ajuste una regresión logística fraccional a los datos. Se adapta bien a los datos porcentuales que están delimitados entre 0 y 100% y están bien justificados teóricamente en muchas áreas de la biología.

Tenga en cuenta que es posible que tenga que dividir todos los valores por 100 para ajustarlos, ya que los programas frecuentemente esperan que los datos oscilen entre 0 y 1. Y, como recomienda Ben Bolker, para abordar los posibles problemas causados ​​por los supuestos estrictos de la distribución binomial con respecto a la varianza, use un distribución cuasibinomial en su lugar.

He hecho algunas suposiciones basadas en su código, como que hay 2 virus que le interesan y que pueden mostrar patrones diferentes (es decir, puede haber una interacción entre el tipo de virus y la cantidad de copias).

Primero, el modelo se ajusta:

dat <- read.csv('Book1.csv')
dat$logcopies <- log10(dat$Copies_per_uL)
dat$Genome_cov_norm <- dat$Genome_cov/100

fit <- glm(Genome_cov_norm ~ logcopies * Virus, data = dat, family = quasibinomial())
summary(fit)


Call:
glm(formula = Genome_cov_norm ~ logcopies * Virus, family = quasibinomial(), 
    data = dat)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-0.55073  -0.13362   0.07825   0.20362   0.70086  

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)  
(Intercept)          -5.9702     2.8857  -2.069   0.0486 *
logcopies             2.3262     1.0961   2.122   0.0435 *
VirusUMAV             2.6147     3.3049   0.791   0.4360  
logcopies:VirusUMAV  -0.6028     1.3173  -0.458   0.6510  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for quasibinomial family taken to be 0.6934319)

    Null deviance: 30.4473  on 29  degrees of freedom
Residual deviance:  2.7033  on 26  degrees of freedom

Si confía en los valores p, el resultado no sugiere que los dos virus difieran significativamente. Esto contrasta con los resultados de @ NickCox a continuación, aunque utilizamos diferentes métodos. No estaría muy seguro de ninguna manera con 30 puntos de datos.

En segundo lugar, la trama:

No es difícil codificar una forma de visualizar la salida usted mismo, pero parece haber un paquete ggPredict que hará la mayor parte del trabajo por usted (no puedo garantizarlo, no lo he probado yo mismo). El código se verá más o menos así:

library(ggiraphExtra)
ggPredict(fit) + theme_bw(base_size = 20) + geom_line(size = 2) 

Actualización: ya no recomiendo el código o la función ggPredict de manera más general. Después de probarlo, descubrí que los puntos trazados no reflejan exactamente los datos de entrada, sino que se cambian por alguna extraña razón (algunos de los puntos trazados estaban por encima de 1 y por debajo de 0). Así que recomiendo codificarlo usted mismo, aunque eso es más trabajo.

mkt - Restablecer a Monica
fuente
77
Respaldo esta respuesta, pero me gustaría aclarar algo: llamaría a esto regresión logística fraccional. Creo que este término sería más ampliamente reconocido. Cuando la mayoría de las personas escuchan "regresión logística", apuesto a que piensan en una variable dependiente 0/1. Una buena respuesta de Stackexchange sobre esta nomenclatura está aquí: stats.stackexchange.com/questions/216122/…
Weiwen Ng
2
@teaelleceecee Evidentemente, primero debe dividir la cobertura por 100.
Nick Cox
44
use family=quasibinomial()para evitar la advertencia (y los problemas subyacentes con suposiciones de varianza demasiado estrictas). Siga los consejos de @ mkt sobre el otro problema.
Ben Bolker
2
Esto puede funcionar, pero me gustaría advertirle a la gente que debe tener una premisa antes de ajustar una función de que sus datos deben seguir esa función. De lo contrario, estás disparando al azar cuando eliges una función de ajuste, y los resultados pueden engañarte.
Carl Witthoft
66
@CarlWitthoft Escuchamos el sermón pero somos pecadores fuera del servicio. ¿Qué premisa anterior te llevó a sugerir una función Heaviside en otros comentarios? La biología aquí no se parece a la transición en un umbral agudo. El hecho de la investigación aquí, según tengo entendido, es que la teoría formal es más débil que los datos. Estoy de acuerdo: si las personas piensan que una función escalonada tiene sentido, deberían encajar en una.
Nick Cox
11

Esta no es una respuesta diferente de @mkt, pero los gráficos en particular no caben en un comentario. Primero ajusto una curva logística en Stata (después de registrar el predictor) a todos los datos y obtengo este gráfico

ingrese la descripción de la imagen aquí

Una ecuación es

100 invlogit(-4,192654 + 1,880951 log10( Copies))

Ahora ajusto las curvas por separado para cada virus en el escenario más simple de virus que define una variable indicadora. Aquí para el registro hay un script Stata:

clear 
input id str9 Subsample   str4 Virus   Genome_cov  Copies_per_uL
1   S1.1_RRAV   RRAV    100 92500
2   S1.2_RRAV   RRAV    100 95900
3   S1.3_RRAV   RRAV    100 92900
4   S2.1_RRAV   RRAV    100 4049.54
5   S2.2_RRAV   RRAV    96.9935 3809
6   S2.3_RRAV   RRAV    94.5054 3695.06
7   S3.1_RRAV   RRAV    3.7235  86.37
8   S3.2_RRAV   RRAV    11.8186 84.2
9   S3.3_RRAV   RRAV    11.0929 95.2
10  S4.1_RRAV   RRAV    0   2.12
11  S4.2_RRAV   RRAV    5.0799  2.71
12  S4.3_RRAV   RRAV    0   2.39
13  S5.1_RRAV   RRAV    4.9503  0.16
14  S5.2_RRAV   RRAV    0   0.08
15  S5.3_RRAV   RRAV    4.4147  0.08
16  S1.1_UMAV   UMAV    5.7666  1.38
17  S1.2_UMAV   UMAV    26.0379 1.72
18  S1.3_UMAV   UMAV    7.4128  2.52
19  S2.1_UMAV   UMAV    21.172  31.06
20  S2.2_UMAV   UMAV    16.1663 29.87
21  S2.3_UMAV   UMAV    9.121   32.82
22  S3.1_UMAV   UMAV    92.903  627.24
23  S3.2_UMAV   UMAV    83.0314 615.36
24  S3.3_UMAV   UMAV    90.3458 632.67
25  S4.1_UMAV   UMAV    98.6696 11180
26  S4.2_UMAV   UMAV    98.8405 12720
27  S4.3_UMAV   UMAV    98.7939 8680
28  S5.1_UMAV   UMAV    98.6489 318200
29  S5.2_UMAV   UMAV    99.1303 346100
30  S5.3_UMAV   UMAV    98.8767 345100
end 

gen log10Copies = log10(Copies)
gen Genome_cov_pr = Genome_cov / 100
encode Virus, gen(virus)
set seed 2803 
fracreg logit Genome_cov_pr log10Copies i.virus, vce(bootstrap, reps(10000)) 

twoway function invlogit(-5.055519 + 1.961538 * x), lc(orange) ra(log10Copies)      ///
|| function invlogit(-5.055519 + 1.233273 + 1.961538 * x), ra(log10Copies) lc(blue) ///
|| scatter Genome_cov_pr log10Copies if Virus == "RRAV", mc(orange) ms(Oh)          ///
|| scatter Genome_cov_pr log10Copies if Virus == "UMAV", mc(blue) ms(+)             ///
legend(order(4 "UMAV" 3 "RRAV") pos(11) col(1) ring(0))                             ///
xla(-1 "0.1" 0 "1" 1 "10" 2 "100" 3 "10{sup:3}" 4 "10{sup:4}" 5 "10{sup:5}")        ///
yla(0 .25 "25" .5 "50" .75 "75" 1 "100", ang(h))                                    ///
ytitle(Genome coverage (%)) xtitle(Genome copies / {&mu}L) scheme(s1color) 

Esto está presionando mucho en un pequeño conjunto de datos, pero el valor P para virus parece ser compatible con el ajuste de dos curvas juntas.

Fractional logistic regression                  Number of obs     =         30
                                                Replications      =     10,000
                                                Wald chi2(2)      =      48.14
                                                Prob > chi2       =     0.0000
Log pseudolikelihood = -6.9603063               Pseudo R2         =     0.6646

-------------------------------------------------------------------------------
              |   Observed   Bootstrap                         Normal-based
Genome_cov_pr |      Coef.   Std. Err.      z    P>|z|     [95% Conf. Interval]
--------------+----------------------------------------------------------------
  log10Copies |   1.961538   .2893965     6.78   0.000     1.394331    2.528745
              |
        virus |
        UMAV  |   1.233273   .5557609     2.22   0.026     .1440018    2.322544
        _cons |  -5.055519   .8971009    -5.64   0.000    -6.813805   -3.297234
-------------------------------------------------------------------------------

ingrese la descripción de la imagen aquí

Nick Cox
fuente
3

Prueba la función sigmoidea . Hay muchas formulaciones de esta forma, incluida una curva logística. La tangente hiperbólica es otra opción popular.

Dadas las tramas, tampoco puedo descartar una función de paso simple. Me temo que no podrá diferenciar entre una función de paso y cualquier cantidad de especificaciones sigmoideas. No tiene ninguna observación en la que su porcentaje esté en el rango del 50%, por lo que la formulación de pasos simples puede ser la opción más parsimoinosa que no funciona peor que los modelos más complejos.

Aksakal
fuente
σ(X)=12(1+tanhX2)
2
@JG "sigmoide" es un término genérico para una curva en S, en lo que a mí respecta, pero tiene razón al señalar un vínculo entre dos especificaciones de un sigmoide
Aksakal
2

Aquí están los ajustes 4PL (logística de 4 parámetros), tanto restringidos como no restringidos, con la ecuación según CA Holstein, M. Griffin, J. Hong, PD Sampson, "Método estadístico para determinar y comparar límites de detección de bioensayos", Anal . Chem 87 (2015) 9795-9801. La ecuación 4PL se muestra en ambas figuras y los significados de los parámetros son los siguientes: a = asíntota inferior, b = factor de pendiente, c = punto de inflexión yd = asíntota superior.

La Figura 1 restringe a a igual a 0% yd igual a 100%:

Fig. 1 restringido a & d

La Figura 2 no tiene restricciones en los 4 parámetros en la ecuación 4PL:

Fig. 2 Sin restricciones

¡Esto fue divertido, no pretendo saber nada biológico y será interesante ver cómo se soluciona todo!

Ed V
fuente
Gracias, esto es realmente útil. Me pregunto, ¿hiciste esto en MATLAB con la función de ajuste?
teaelleceecee
1
Usé Igor Pro con la función de usuario definida por el usuario que se muestra en las figuras. He usado Igor Pro y su predecesor (Igor) desde 1988, pero muchos otros programas pueden ajustar la curva, por ejemplo, Origin Pro y el muy económico Kaleidagraph. Y parece que tienes acceso R y (¿posiblemente?) A Matlab, de los cuales no sé nada, excepto que son extremadamente capaces. ¡El mejor de los éxitos con esto y espero que tenga buenas noticias la próxima vez que discuta las cosas con los supervisores! Además, ¡gracias por publicar los datos!
Ed V
2

Extraje los datos de su diagrama de dispersión, y mi búsqueda de ecuaciones arrojó una ecuación de tipo logístico de 3 parámetros como un buen candidato: "y = a / (1.0 + b * exp (-1.0 * c * x))", donde " x "es la base de registro 10 por su parcela. Los parámetros ajustados fueron a = 9.0005947126706630E + 01, b = 1.2831794858584102E + 07 yc = 6.6483431489473155E + 00 para mis datos extraídos, un ajuste de los datos originales (log 10 x) debería producir resultados similares si vuelve a ajustar los datos originales usando mis valores como estimaciones de parámetros iniciales. Los valores de mis parámetros están produciendo R-cuadrado = 0.983 y RMSE = 5.625 en los datos extraídos.

trama

EDITAR: ahora que la pregunta se ha editado para incluir los datos reales, aquí hay un gráfico que utiliza la ecuación de 3 parámetros y las estimaciones de parámetros iniciales anteriores.

plot2

James Phillips
fuente
Parece que ha habido un error en la extracción de datos: tiene un montón de valores de porcentaje negativos. Además, sus valores máximos son de aproximadamente 90% en lugar de 100% como en el gráfico original. Puede tener todo compensado en aproximadamente un 10% por alguna razón.
mkt
Meh: se trata de datos extraídos semi-manualmente, se requieren los datos originales. Por lo general, esto es suficiente para las búsquedas de ecuaciones y, por supuesto, no para los resultados finales, por lo que dije que use mis valores de parámetros de extracción o ajuste como estimaciones de parámetros iniciales en los datos originales.
James Phillips
Tenga en cuenta que como los datos reales se han agregado a la publicación, he actualizado esta respuesta utilizando los datos actualizados.
James Phillips
Solo para reiterar: la aplicación de, por ejemplo, una función Heaviside, puede producir valores de error similares.
Carl Witthoft
1
@JamesPhillips Intentaré hacerlo (Heaviside -> barras de error o equivalente)
Carl Witthoft
2

Como tuve que abrir mi boca grande sobre Heaviside, aquí están los resultados. Configuré el punto de transición a log10 (viruscopies) = 2.5. Luego calculé las desviaciones estándar de las dos mitades del conjunto de datos, es decir, el Heaviside está asumiendo que los datos en ambos lados tienen todas las derivadas = 0.

Dev. Estándar del lado derecho = 4.76
Dev. Estándar del lado izquierdo = 7.72

Como resulta que hay 15 muestras en cada lote, el dev estándar es la media, o 6.24.

Suponiendo que el "RMSE" citado en otras respuestas es "error RMS" en general, la función Heaviside parecería funcionar al menos tan bien, si no mejor, que la mayoría de la "curva Z" (tomada de la nomenclatura de respuesta fotográfica) aquí.

editar

Gráfico inútil, pero solicitado en los comentarios:

Ajuste de la curva de Heaviside

Carl Witthoft
fuente
¿Podría publicar un modelo y un diagrama de dispersión de manera similar a lo que se hizo en las otras respuestas? Tengo mucha curiosidad por ver estos resultados y compararlos. Agregue también los valores RMSE y R-cuadrado para comparar. Personalmente, nunca he usado la función Heaviside y encuentro esto muy interesante.
James Phillips
@JamesPhillips Realmente no hay nada que mostrar, obviamente, el diagrama de dispersión es el mismo; todo lo que hice fue seleccionar manualmente el punto de transición y tomar la media bruta de cada conjunto de puntos (izquierda y derecha). no estoy seguroR2Tiene mucho significado aquí.
Carl Witthoft
Mi significado era hacer una trama similar a las hechas en las otras respuestas, con el propósito de compararlas directamente con esas respuestas.
James Phillips
2
@JamesPhillips, te quedan dos deseos. Elija sabiamente :-)
Carl Witthoft
Gracias por la trama. Observo que en todas las gráficas en otras respuestas, la ecuación graficada sigue la forma curva de los datos en la parte superior derecha; su no, como es la naturaleza de la función Heaviside. Esto visualmente parece contradecir su afirmación de que la función Heaviside funcionaría tan bien como las ecuaciones publicadas en las otras respuestas, por lo que previamente solicité los valores RMSE y R-cuadrado, sospeché que la función Heaviside no seguiría la forma de los datos en esta región y podrían arrojar peores valores para las estadísticas de ajuste.
James Phillips