Considere los cuatro porcentajes a continuación, representados como float
números:
13.626332%
47.989636%
9.596008%
28.788024%
-----------
100.000000%
Necesito representar estos porcentajes como números enteros. Si simplemente lo uso Math.round()
, termino con un total de 101%.
14 + 48 + 10 + 29 = 101
Si lo uso parseInt()
, termino con un total de 97%.
13 + 47 + 9 + 28 = 97
¿Qué es un buen algoritmo para representar cualquier número de porcentajes como números enteros mientras se mantiene un total del 100%?
Editar : después de leer algunos de los comentarios y respuestas, claramente hay muchas maneras de resolverlo.
En mi opinión, para permanecer fiel a los números, el resultado "correcto" es el que minimiza el error general, definido por la cantidad de error que se introduciría en relación con el valor real:
value rounded error decision
----------------------------------------------------
13.626332 14 2.7% round up (14)
47.989636 48 0.0% round up (48)
9.596008 10 4.0% don't round up (9)
28.788024 29 2.7% round up (29)
En caso de empate (3.33, 3.33, 3.33) se puede tomar una decisión arbitraria (por ejemplo, 3, 4, 3).
fuente
Respuestas:
Dado que ninguna de las respuestas aquí parece resolverlo correctamente, aquí está mi versión semi-ofuscada usando underscorejs :
fuente
Hay muchas maneras de hacer esto, siempre que no le preocupe la dependencia de los datos decimales originales.
El primer método y quizás el más popular sería el método de remanente más grande
Que es básicamente:
En su caso, sería así:
Si tomas las partes enteras, obtienes
que suma 97 y desea agregar tres más. Ahora nos fijamos en las partes decimales, que son
y tome los más grandes hasta que el total llegue a 100. Entonces obtendrá:
Alternativamente, puede simplemente elegir mostrar un lugar decimal en lugar de valores enteros. Entonces, los números serían 48.3 y 23.9, etc. Esto reduciría mucho la varianza de 100.
fuente
Probablemente la "mejor" forma de hacer esto (citado ya que "mejor" es un término subjetivo) es mantener una cuenta corriente (no integral) de dónde se encuentra, y redondear eso valor.
Luego, utilícelo junto con el historial para determinar qué valor debe usarse. Por ejemplo, usando los valores que dio:
En cada etapa, no redondeas el número en sí. En cambio, redondeas el acumulado valor y calcula el mejor entero que alcanza ese valor desde la línea de base anterior: esa línea de base es el valor acumulado (redondeado) de la fila anterior.
Esto funciona porque estás no perder información en cada etapa, sino más bien el uso de la información de forma más inteligente. Los valores redondeados 'correctos' están en la columna final y puede ver que suman 100.
Puede ver la diferencia entre esto y redondear ciegamente cada valor, en el tercer valor anterior. Si bien
9.596008
normalmente se redondearía a10
, el acumulado71.211976
se redondea correctamente a71
- esto significa que solo9
es necesario agregar a la línea de base anterior de62
.Esto también funciona para una secuencia "problemática" como tres valores aproximados , donde uno
1/3
de ellos debe redondearse:fuente
26, 25, 26, 23
, el segundo1, 0, 1, 0, 1, 0, ...
.El objetivo del redondeo es generar la menor cantidad de error. Cuando redondeas un valor único, ese proceso es simple y directo y la mayoría de las personas lo entienden fácilmente. Cuando redondea varios números al mismo tiempo, el proceso se vuelve más complicado: debe definir cómo se combinarán los errores, es decir, qué se debe minimizar.
La respuesta bien votada por Varun Vohra minimiza la suma de los errores absolutos, y es muy simple de implementar. Sin embargo, hay casos extremos que no maneja: cuál debería ser el resultado del redondeo
24.25, 23.25, 27.25, 25.25
? Uno de esos debe redondearse hacia arriba en lugar de hacia abajo. Probablemente elegiría arbitrariamente el primero o el último de la lista.Quizás sea mejor usar el error relativo en lugar del absoluto error . Redondeando 23.25 hasta 24 lo cambia en un 3.2% mientras que redondeando 27.25 hasta 28 solo lo cambia en un 2.8%. Ahora hay un claro ganador.
Es posible ajustar esto aún más. Una técnica común es cuadrar cada error, para que los errores grandes cuenten desproporcionadamente más que los pequeños. También usaría un divisor no lineal para obtener el error relativo: no parece correcto que un error al 1% sea 99 veces más importante que un error al 99%. En el siguiente código, he usado la raíz cuadrada.
El algoritmo completo es el siguiente:
Es posible que aún tenga más de una combinación con la misma suma de errores, por ejemplo
33.3333333, 33.3333333, 33.3333333
. Esto es inevitable, y el resultado será completamente arbitrario. El código que doy a continuación prefiere redondear los valores a la izquierda.Poner todo junto en Python se ve así.
Como puede ver con el último ejemplo, este algoritmo aún es capaz de entregar resultados no intuitivos. A pesar de que 89.0 no necesita redondeo alguno, uno de los valores en esa lista necesitaba ser redondeado; el error relativo más bajo resulta de redondear ese gran valor en lugar de las alternativas mucho más pequeñas.
Esta respuesta originalmente abogó por pasar por todas las combinaciones posibles de redondeo arriba / abajo, pero como se señaló en los comentarios, un método más simple funciona mejor. El algoritmo y el código reflejan esa simplificación.
fuente
if actual == 0: return 0
aerror_gen
funciona muy bien.isclose
método al principio deround_to_100
?NO sume los números redondeados. Vas a tener resultados inexactos. El total podría estar significativamente apagado dependiendo del número de términos y la distribución de partes fraccionarias.
Muestra los números redondeados pero suma los valores reales. Dependiendo de cómo esté presentando los números, la forma real de hacerlo podría variar. De esa manera obtienes
De cualquier manera que vaya, tendrá discrepancias. No hay forma en su ejemplo de mostrar números que sumen 100 sin "redondear" un valor de la manera incorrecta (el mínimo error sería cambiar 9.596 a 9)
EDITAR
Debe elegir entre uno de los siguientes:
La mayoría de las veces, cuando se trata de porcentajes n. ° 3, es la mejor opción porque es más obvio cuando el total es igual al 101% que cuando los elementos individuales no suman 100, y usted mantiene los elementos individuales con precisión. "Redondear" 9.596 a 9 es inexacto en mi opinión.
Para explicar esto, a veces agrego una nota al pie de página que explica que los valores individuales se redondean y pueden no sumar el 100%; cualquiera que entienda el redondeo debería poder entender esa explicación.
fuente
Escribí un asistente de redondeo de la versión C #, el algoritmo es el mismo que la respuesta de Varun Vohra , espero que ayude.
Pasa la siguiente prueba de Unidad:
fuente
Puede intentar realizar un seguimiento de su error debido al redondeo y luego redondear contra el grano si el error acumulado es mayor que la parte fraccionaria del número actual.
No estoy seguro de si esto funcionaría en general, pero parece funcionar de manera similar si se invierte el orden:
Estoy seguro de que hay casos extremos en los que esto podría romperse, pero cualquier enfoque será al menos algo arbitrario, ya que básicamente está modificando sus datos de entrada.
fuente
Una vez escribí una herramienta no redondeada, para encontrar la perturbación mínima de un conjunto de números para que coincida con un objetivo. Era un problema diferente, pero uno podría en teoría usar una idea similar aquí. En este caso, tenemos un conjunto de opciones.
Por lo tanto, para el primer elemento, podemos redondearlo a 14 o a 13. El costo (en un sentido de programación de enteros binarios) de hacerlo es menor para el redondeo hacia arriba que hacia abajo, porque el redondeo hacia abajo requiere que mover ese valor a una distancia mayor. Del mismo modo, podemos redondear cada número hacia arriba o hacia abajo, por lo que hay un total de 16 opciones entre las que debemos elegir.
Normalmente resolvería el problema general en MATLAB, aquí usando bintprog, una herramienta de programación de enteros binarios, pero solo hay algunas opciones para probar, por lo que es bastante fácil con bucles simples probar cada una de las 16 alternativas. Por ejemplo, supongamos que redondeamos este conjunto como:
El error absoluto total realizado es 1.25266. Se puede reducir ligeramente mediante el siguiente redondeo alternativo:
De hecho, esta será la solución óptima en términos del error absoluto. Por supuesto, si hubiera 20 términos, el espacio de búsqueda será de tamaño 2 ^ 20 = 1048576. Para 30 o 40 términos, ese espacio será de tamaño significativo. En ese caso, necesitaría usar una herramienta que pueda buscar eficientemente el espacio, tal vez usando un esquema de ramificación y enlace.
fuente
Creo que lo siguiente logrará lo que buscas
Una última cosa, ejecuté la función usando los números originalmente dados en la pregunta para comparar con la salida deseada
Esto era diferente a lo que la pregunta quería => [48, 29, 14, 9]. No pude entender esto hasta que miré el margen de error total
Esencialmente, el resultado de mi función en realidad introduce la menor cantidad de error.
Violín aquí
fuente
No estoy seguro de qué nivel de precisión necesita, pero lo que haría es simplemente agregar 1 a los primeros
n
números, quen
es el límite máximo de la suma total de decimales. En este caso3
, es decir , agregaría 1 a los primeros 3 elementos y colocaría el resto. Por supuesto, esto no es súper preciso, algunos números pueden redondearse hacia arriba o hacia abajo cuando no debería, pero funciona bien y siempre dará como resultado el 100%.Entonces
[ 13.626332, 47.989636, 9.596008, 28.788024 ]
sería[14, 48, 10, 28]
porqueMath.ceil(.626332+.989636+.596008+.788024) == 3
Siempre puede informar a los usuarios que los números son redondeados y pueden no ser muy precisos ...
fuente
Si lo está redondeando, no hay una buena manera de hacerlo exactamente igual en todos los casos.
Puedes tomar la parte decimal de los N porcentajes que tienes (en el ejemplo que diste es 4).
Suma las partes decimales. En su ejemplo, tiene un total de parte fraccionaria = 3.
Coloca el techo en los 3 números con las fracciones más altas y coloca el resto.
(Perdón por las ediciones)
fuente
Si realmente debe redondearlos, ya hay muy buenas sugerencias aquí (resto más grande, menos error relativo, etc.).
También hay una buena razón para no redondear (obtendrá al menos un número que "se ve mejor" pero está "equivocado") y cómo resolverlo (advertir a sus lectores) y eso es lo que hago.
Permítanme agregar la parte del número "incorrecta".
Suponga que tiene tres eventos / entidades / ... con algunos porcentajes que aproxima como:
Más adelante los valores cambian ligeramente, a
La primera tabla tiene el problema ya mencionado de tener un número "incorrecto": 33.34 está más cerca de 33 que de 34.
Pero ahora tienes un error más grande. Comparando el día 2 con el día 1, el valor de porcentaje real para A aumentó, en un 0.01%, pero la aproximación muestra una disminución en un 1%.
Ese es un error cualitativo, probablemente bastante peor que el error cuantitativo inicial.
Se podría idear una aproximación para todo el conjunto, pero es posible que tenga que publicar datos el primer día, por lo que no sabrá sobre el segundo día. Entonces, a menos que realmente, realmente, deba aproximarse, probablemente sea mejor que no.
fuente
compruebe si esto es válido o no, en lo que respecta a mis casos de prueba, puedo hacer que esto funcione.
digamos que número es k;
fuente
He implementado el método de la respuesta de Varun Vohra aquí para listas y dictados.
fuente
Aquí hay una implementación Python más simple de la respuesta @ varun-vohra:
Es necesario
math
,itertools
,operator
.fuente
Para aquellos que tienen los porcentajes en una serie de pandas, aquí está mi implementación del método de resto más grande (como en la respuesta de Varun Vohra ), donde incluso puede seleccionar los decimales a los que desea redondear.
fuente
Este es un caso para el redondeo de los banqueros, también conocido como 'redondo medio par'. Es compatible con BigDecimal. Su propósito es asegurar que el redondeo se equilibre, es decir, no favorezca ni al banco ni al cliente.
fuente