Quiero escribir un código para contar y sumar cualquier serie de números positivos y negativos.
Los números son positivos o negativos (sin cero).
He escrito códigos con bucles for . ¿Hay alguna alternativa creativa?
set.seed(100) x <- round(rnorm(20, sd = 0.02), 3) x = [-0.01, 0.003, -0.002, 0.018, 0.002, 0.006, -0.012, 0.014, -0.017, -0.007, 0.002, 0.002, -0.004, 0.015, 0.002, -0.001, -0.008, 0.01, -0.018, 0.046] sign_indicator <- ifelse(x > 0, 1,-1) number_of_sequence <- rep(NA, 20) n <- 1 for (i in 2:20) { if (sign_indicator[i] == sign_indicator[i - 1]) { n <- n + 1 } else{ n <- 1 } number_of_sequence[i] <- n } number_of_sequence[1] <- 1 ############################# summation <- rep(NA, 20) for (i in 1:20) { summation[i] <- sum(x[i:(i + 1 - number_of_sequence[i])]) } sign_indicator = [1 if i > 0 else -1 for i in X] number_of_sequence = [1] N = 1 for i in range(1, len(sign_indicator)): if sign_indicator[i] == sign_indicator[i - 1]: N += 1 else: N = 1 number_of_sequence.append(N) ############################# summation = [] for i in range(len(X)): if number_of_sequence[i] == 1: summation.append(X[i]) else: summation.append(sum(X[(i + 1 - number_of_sequence[i]):(i + 1)])) x n_of_sequence sum 1 -0.010 1 -0.010 2 0.003 1 0.003 3 -0.002 1 -0.002 4 0.018 1 0.018 5 0.002 2 0.020 6 0.006 3 0.026 7 -0.012 1 -0.012 8 0.014 1 0.014 9 -0.017 1 -0.017 10 -0.007 2 -0.024 11 0.002 1 0.002 12 0.002 2 0.004 13 -0.004 1 -0.004 14 0.015 1 0.015 15 0.002 2 0.017 16 -0.001 1 -0.001 17 -0.008 2 -0.009 18 0.010 1 0.010 19 -0.018 1 -0.018 20 0.046 1 0.046En R, puede usar data.table s rleid para crear grupos con series de números positivas y negativas y luego crear una secuencia de filas en cada grupo y hacer una suma acumulativa de los valores de x .
library(data.table) df <- data.table(x) df[, c("n_of_sequence", "sum") := list(seq_len(.N), cumsum(x)), by = rleid(sign(x))] df # x n_of_sequence sum # 1: -0.010 1 -0.010 # 2: 0.003 1 0.003 # 3: -0.002 1 -0.002 # 4: 0.018 1 0.018 # 5: 0.002 2 0.020 # 6: 0.006 3 0.026 # 7: -0.012 1 -0.012 # 8: 0.014 1 0.014 # 9: -0.017 1 -0.017 #10: -0.007 2 -0.024 #11: 0.002 1 0.002 #12: 0.002 2 0.004 #13: -0.004 1 -0.004 #14: 0.015 1 0.015 #15: 0.002 2 0.017 #16: -0.001 1 -0.001 #17: -0.008 2 -0.009 #18: 0.010 1 0.010 #19: -0.018 1 -0.018 #20: 0.046 1 0.046 También podemos usar rleid en dplyr para crear grupos y hacer lo mismo.
library(dplyr) df %>% group_by(gr = data.table::rleid(sign(x))) %>% mutate(n_of_sequence = row_number(), sum = cumsum(x))Puede calcular las longitudes de ejecución de cada signo usando rle desde la base hasta y hacer algo como esto.
set.seed(0) z <- round(rnorm(20, sd = 0.02), 3) run_lengths <- rle(sign(z))$lengths run_lengths # [1] 1 1 1 3 1 1 2 2 1 2 2 1 1 1 Para obtener n_of_sequence
n_of_sequence <- run_lengths %>% map(seq) %>% unlist n_of_sequence # [1] 1 1 1 1 2 3 1 1 1 2 1 2 1 1 2 1 2 1 1 1Finalmente, para obtener las sumas de las sucesiones,
start <- cumsum(c(1,run_lengths)) start <- start[-length(start)] # start points of each series map2(start,run_lengths,~cumsum(z[.x:(.x+.y-1)])) %>% unlist() # [1] -0.010 0.003 -0.002 0.018 0.020 0.026 -0.012 0.014 -0.017 -0.024 # [11] 0.002 0.004 -0.004 0.015 0.017 -0.001 -0.009 0.010 -0.018 0.046Aquí hay una función simple sin bucle en R:
count_and_sum <- function(x) { runs <- rle((x > 0) * 1)$lengths groups <- split(x, rep(1:length(runs), runs)) output <- function(group) data.frame(x = group, n = seq_along(group), sum = cumsum(group)) result <- as.data.frame(do.call(rbind, lapply(groups, output))) `rownames<-`(result, 1:nrow(result)) }Entonces puedes hacer:
set.seed(100) x <- round(rnorm(20, sd = 0.02), 3) count_and_sum(x) #> xn sum #> 1 -0.010 1 -0.010 #> 2 0.003 1 0.003 #> 3 -0.002 1 -0.002 #> 4 0.018 1 0.018 #> 5 0.002 2 0.020 #> 6 0.006 3 0.026 #> 7 -0.012 1 -0.012 #> 8 0.014 1 0.014 #> 9 -0.017 1 -0.017 #> 10 -0.007 2 -0.024 #> 11 0.002 1 0.002 #> 12 0.002 2 0.004 #> 13 -0.004 1 -0.004 #> 14 0.015 1 0.015 #> 15 0.002 2 0.017 #> 16 -0.001 1 -0.001 #> 17 -0.008 2 -0.009 #> 18 0.010 1 0.010 #> 19 -0.018 1 -0.018 #> 20 0.046 1 0.046Creado el 2020-02-16 por el paquete reprex (v0.3.0)
Aquí hay una solución tidyverse simple ...
library(tidyverse) #or just dplyr and tidyr set.seed(100) x <- round(rnorm(20, sd = 0.02), 3) df <- tibble(x = x) %>% mutate(seqno = cumsum(c(1, diff(sign(x)) != 0))) %>% #identify sequence ids group_by(seqno) %>% #group by sequences mutate(n_of_sequence = row_number(), #count row numbers for each group sum = cumsum(x)) %>% #cumulative sum for each group ungroup() %>% select(-seqno) #remove sequence id df # A tibble: 20 x 3 x n_of_sequence sum <dbl> <int> <dbl> 1 -0.01 1 -0.01 2 0.003 1 0.003 3 -0.002 1 -0.002 4 0.018 1 0.018 5 0.002 2 0.0200 6 0.006 3 0.026 7 -0.012 1 -0.012 8 0.014 1 0.014 9 -0.017 1 -0.017 10 -0.007 2 -0.024 11 0.002 1 0.002 12 0.002 2 0.004 13 -0.004 1 -0.004 14 0.015 1 0.015 15 0.002 2 0.017 16 -0.001 1 -0.001 17 -0.008 2 -0.009 18 0.01 1 0.01 19 -0.018 1 -0.018 20 0.046 1 0.046En cuanto a Python, alguien encontrará una solución utilizando la biblioteca pandas. Mientras tanto, aquí hay una propuesta simple:
class Combiner: def __init__(self): self.index = self.seq_index = self.summation = 0 def combine(self, value): self.index += 1 if value * self.summation <= 0: self.seq_index = 1 self.summation = value else: self.seq_index += 1 self.summation += value return self.index, value, self.seq_index, self.summation c = Combiner() lst = [c.combine(v) for v in x] for t in lst: print(f"{t[0]:3} {t[1]:7.3f} {t[2]:3} {t[3]:7.3f}")Producción:
1 -0.010 1 -0.010 2 0.003 1 0.003 3 -0.002 1 -0.002 4 0.018 1 0.018 5 0.002 2 0.020 6 0.006 3 0.026 7 -0.012 1 -0.012 8 0.014 1 0.014 9 -0.017 1 -0.017 10 -0.007 2 -0.024 11 0.002 1 0.002 12 0.002 2 0.004 13 -0.004 1 -0.004 14 0.015 1 0.015 15 0.002 2 0.017 16 -0.001 1 -0.001 17 -0.008 2 -0.009 18 0.010 1 0.010 19 -0.018 1 -0.018 20 0.046 1 0.046Si necesita listas separadas, puede hacerlo
idxs, vals, seqs, sums = (list(tpl) for tpl in zip(*lst))o, si los iteradores están bien, simplemente
idxs, vals, seqs, sums = zip(*lst)(explicación aquí )
Dos soluciones perezosas diferentes en Python, usando el módulo itertools .
from itertools import accumulate, groupby result = ( item for _, group in groupby(x, key=lambda n: n < 0) for item in enumerate(accumulate(group), 1) ) from itertools import accumulate def sign_count_sum(count_sum, value): count, prev_sum = count_sum same_sign = (prev_sum < 0) is (value < 0) if same_sign: return count + 1, prev_sum + value else: return 1, value result = accumulate(x, sign_count_sum, initial=(0, 0)) next(result) # needed to skip the initial (0, 0) item El argumento de palabra clave initial se agregó en Python 3.8. En versiones anteriores, puede usar itertools.chain para anteponer la tupla (0,0):
result = accumulate(chain([(0, 0)], x), sign_count_sum)La salida es la esperada:
for (i, v), (c, s) in zip(enumerate(x), result): print(f"{i:3} {v:7.3f} {c:3} {s:7.3f}") 0 -0.010 1 -0.010 1 0.003 1 0.003 2 -0.002 1 -0.002 3 0.018 1 0.018 4 0.002 2 0.020 5 0.006 3 0.026 6 -0.012 1 -0.012 7 0.014 1 0.014 8 -0.017 1 -0.017 9 -0.007 2 -0.024 10 0.002 1 0.002 11 0.002 2 0.004 12 -0.004 1 -0.004 13 0.015 1 0.015 14 0.002 2 0.017 15 -0.001 1 -0.001 16 -0.008 2 -0.009 17 0.010 1 0.010 18 -0.018 1 -0.018 19 0.046 1 0.046En Python, además de definir una clase para almacenar las variables de memoria, puede usar un cierre para lograr lo mismo.
def run(): count = 0 last_sign = 0 def sign(i): return 1 if i > 0 else -1 def f(i): nonlocal count nonlocal last_sign if sign(i) == last_sign: count = count+1 else: last_sign = sign(i) count = 1 return count return f f = run() y = [f(i) for i in x]Tenga en cuenta que esto funciona solo para Python 3 (en Python 2 creo que no puede modificar la variable de cierre de esta manera). Algo similar para la suma también.
Las otras soluciones se ven bien, pero realmente no necesita usar características de lenguaje sofisticadas o funciones de biblioteca para este problema simple.
result, prev = [], None for idx, cur in enumerate(x): if not prev or (prev > 0) != (cur > 0): n, summation = 1, cur else: n, summation = n + 1, summation + cur result.append((idx, cur, n, summation)) prev = cur Como puede ver, realmente no necesita la lista sign_indicator , dos bucles for o la función de range como en el fragmento de la sección de preguntas.
Si desea que el índice comience desde 1, use enumerate(x, 1) en lugar de enumerate(x)
Para ver el resultado, puede ejecutar el siguiente código
for idx, num, length, summation in result: print(f"{idx:>2d} {num:.3f} {length:>2d} {summation:.3f}")En R, también podrías hacer:
# DATA set.seed(100) x <- round(rnorm(20, sd = 0.02), 3) library(data.table) dt <- data.table(x = x) # Create Positive or Negative variable dt$x_logical <- ifelse(dt$x > 0, "P", "N") # Create a reference data.frame/table to keep continuous counts seq_dt <- data.frame(val = rle(x = dt$x_logical)$lengths) seq_dt$id <- 1:nrow(seq_dt) # Map id in the main data.table and get cumulative sum dt$id <- rep(seq_dt$id, seq_dt$val) dt[, csum := cumsum(x), by = "id"] x x_logical id csum 1: -0.010 N 1 -0.010 2: 0.003 P 2 0.003 3: -0.002 N 3 -0.002 4: 0.018 P 4 0.018 5: 0.002 P 4 0.020 6: 0.006 P 4 0.026 7: -0.012 N 5 -0.012 8: 0.014 P 6 0.014 9: -0.017 N 7 -0.017 10: -0.007 N 7 -0.024 11: 0.002 P 8 0.002 12: 0.002 P 8 0.004 13: -0.004 N 9 -0.004 14: 0.015 P 10 0.015 15: 0.002 P 10 0.017 16: -0.001 N 11 -0.001 17: -0.008 N 11 -0.009 18: 0.010 P 12 0.010 19: -0.018 N 13 -0.018 20: 0.046 P 14 0.046Aquí hay otro enfoque base R:
data.frame(x, n = sequence(rle(sign(x))$lengths), sum = Reduce(function(x, y) if (sign(x) == sign(y)) x + y else y, x, accumulate = TRUE)) xn sum 1 -0.010 1 -0.010 2 0.003 1 0.003 3 -0.002 1 -0.002 4 0.018 1 0.018 5 0.002 2 0.020 6 0.006 3 0.026 7 -0.012 1 -0.012 8 0.014 1 0.014 9 -0.017 1 -0.017 10 -0.007 2 -0.024 11 0.002 1 0.002 12 0.002 2 0.004 13 -0.004 1 -0.004 14 0.015 1 0.015 15 0.002 2 0.017 16 -0.001 1 -0.001 17 -0.008 2 -0.009 18 0.010 1 0.010 19 -0.018 1 -0.018 20 0.046 1 0.046Creo que un bucle sería más fácil de leer, pero solo por diversión, aquí hay una solución en Python usando recursividad:
x = [-0.01, 0.003, -0.002, 0.018, 0.002, 0.006, -0.012, 0.014, -0.017, -0.007, 0.002, 0.002, -0.004, 0.015, 0.002, -0.001, -0.008, 0.01, -0.018, 0.046] def sign(number): return 1 if number > 0 else -1 def sum_previous(pos, result=None): if not result: result = x[pos] else: result += x[pos] if pos == 0 or sign(x[pos]) != sign(x[pos-1]): return result else: return sum_previous(pos-1, result) results = [sum_previous(i) for i in range(len(x))] print(results)Una respuesta simple de Python, ignora el caso 0:
x = [-0.01, 0.003, -0.002, 0.018, 0.002, 0.006, -0.012, 0.014, -0.017, -0.007, 0.002, 0.002, -0.004, 0.015, 0.002, -0.001, -0.008, 0.01, -0.018, 0.046] count = 0 sign_positive = x[0] > 0 sign_count = [] for n in x: # the idea is to keep track of the sign and increment the # count if it agrees with the current number we are looking at if (n > 0 and sign_positive) or (n < 0 and not sign_positive): count = count + 1 # if it does not, the count goes back to 1 else: count = 1 # Whether we increased the count or not, we update whether the # sign was positive or negative sign_positive = n > 0 sign_count.append(count) # This is just to reproduce the output # (although I find the last repetition of the number unnecessary) results = list(zip(x, sign_count)) for i, result in enumerate(results): print(f"{i: >2d} {result[0]: .3f} {result[1]: >2d} {result[0]: .3f}") 0 -0.010 1 -0.010 1 0.003 1 0.003 2 -0.002 1 -0.002 3 0.018 1 0.018 4 0.002 2 0.002 5 0.006 3 0.006 6 -0.012 1 -0.012 7 0.014 1 0.014 8 -0.017 1 -0.017 9 -0.007 2 -0.007 10 0.002 1 0.002 11 0.002 2 0.002 12 -0.004 1 -0.004 13 0.015 1 0.015 14 0.002 2 0.002 15 -0.001 1 -0.001 16 -0.008 2 -0.008 17 0.010 1 0.010 18 -0.018 1 -0.018 19 0.046 1 0.046Una solución un poco más sofisticada, también se ocupa del caso 0:
# To test the 0 case I am changing two numbers to 0 x = [-0.01, 0.003, -0.002, 0.018, 0.002, 0.006, -0.012, 0.014, -0.017, -0.007, 0, 0, -0.004, 0.015, 0.002, -0.001, -0.008, 0.01, -0.018, 0.046] # The rest is similar count = 0 # This time we are using a nested ternary assignment # to account for the case of 0 # This would be more readable as a function, # but what it does is simple # It returns None if n is 0, # True if it is larger than 0 # and False if it less than 0 sign_positive = None if n == 0 else False if n < 0 else True sign_count = [] for n in x: # We add the case of 0 by adding a third condition where # sign_positive was None (meaning the previous # number was 0) and the current number is 0. if (n > 0 and sign_positive) or \ (n < 0 and not sign_positive) or \ (n == 0 and sign_positive == None): count = count + 1 else: count = 1 sign_positive = None if n == 0 else False if n < 0 else True sign_count.append(count) results = list(zip(x, sign_count)) for i, result in enumerate(results): print(f"{i: >2d} {result[0]: .3f} {result[1]: >2d} {result[0]: .3f}") 0 -0.010 1 -0.010 1 0.003 1 0.003 2 -0.002 1 -0.002 3 0.018 1 0.018 4 0.002 2 0.002 5 0.006 3 0.006 6 -0.012 1 -0.012 7 0.014 1 0.014 8 -0.017 1 -0.017 9 -0.007 2 -0.007 10 0.000 1 0.000 11 0.000 2 0.000 12 -0.004 3 -0.004 13 0.015 1 0.015 14 0.002 2 0.002 15 -0.001 1 -0.001 16 -0.008 2 -0.008 17 0.010 1 0.010 18 -0.018 1 -0.018 19 0.046 1 0.046