TransWikia.com

pandas rellenar columna condicionalmente entre varios valores de otra columna

Stack Overflow en español Asked by Durgeoble on November 18, 2021

Tengo un dataframe que contiene una columna de eventos y quiero añadirle una de estado de este modo:

╔═════════════╦═════════════╗
║ eventType   ║ estado      ║
╠═════════════╬═════════════╣
║ stop        ║ 0           ║
╠═════════════╬═════════════╣
║ algo        ║ 1           ║
╠═════════════╬═════════════╣
║ algo        ║ 1           ║
╠═════════════╬═════════════╣
║ otra cosa   ║ 1           ║
╠═════════════╬═════════════╣
║ evento      ║ 1           ║
╠═════════════╬═════════════╣
║             ║ 1           ║
╠═════════════╬═════════════╣
║ algo        ║ 1           ║
╠═════════════╬═════════════╣
║ otra cosa   ║ 1           ║
╠═════════════╬═════════════╣
║ start       ║ 1           ║
╠═════════════╬═════════════╣
║             ║ 0           ║
╠═════════════╬═════════════╣
║ algo        ║ 0           ║
╠═════════════╬═════════════╣
║ evento      ║ 0           ║
╠═════════════╬═════════════╣
║ otro evento ║ 0           ║
╠═════════════╬═════════════╣
║ otra cosa   ║ 0           ║
╠═════════════╬═════════════╣
║ stop        ║ 0           ║
╠═════════════╬═════════════╣
║ evento      ║ 1           ║
╠═════════════╬═════════════╣
║ evento      ║ 1           ║
╠═════════════╬═════════════╣
║ otro evento ║ 1           ║
╠═════════════╬═════════════╣
║ algo        ║ 1           ║
╠═════════════╬═════════════╣
║ start       ║ 1           ║
╠═════════════╬═════════════╣
║ algo algo   ║ 0           ║
╠═════════════╬═════════════╣
║ lo que sea  ║ 0           ║
╠═════════════╬═════════════╣
║ evento      ║ 0           ║
╚═════════════╩═════════════╝

la idea es que cuando encuentre un start empiece a rellenar con 1 y cuando se encuentre un stop con 0 empezando a rellenar con 0 hasta el primer start.
La columna de eventos tiene varios eventos que paran la máquina y varios que la ponen en marcha (aunque la mayoria tienen stop o start en el nombre)
¿hay algún modo de hacerlo sin usar un bucle for?

Solucionado:
Al final he hecho una función:

actual = 0
def def_estado(row):
    global actual
    events = {'start': 1, 'stop': 0}
    if row['eventType'] in events:
        actual = events[row['eventType']]
    return actual

Y luego se la paso a la la columna con apply

df['estado'] = df.apply(def_estado, axis=1)

2 Answers

Es rara la exigencia de no usar for.

Nota: No me queda claro en que orden vienen los eventos, por lo tanto, dare dos soluciones

Eventos en orden cronologico

Se supone que los eventos están listados en orden cronológico: el primer evento en la columna es el más antiguo.

La función value retorna el valor de relleno, segùn el tipo de evento.

El diccionario opciones tiene como llave el texto del evento y el valor a retornar. También tiene una entrada "last", para guardar el último valor retornado.

La función map aplica la función value a toda la columna del Dataframe, produciendo un iterador. La función list se encarga de transformar lo anterior en una lista. Una vez que tenemos la lista, podemos agregarla como columna al DataFrame por simple asignación.

import pandas as pd

data = ["Inicio", "start", "fecha", "fecha",  "stop", "fecha", "fecha", "start", "fecha", "fin"]

df =  pd.DataFrame(data, columns=["EventType"])

opciones = dict(stop=0, start=1, last=0)
def value(texto):
    if texto in opciones:
        opciones['last'] = opciones[texto]
    return opciones['last']
            
df['estado'] = list(map(value, df["EventType"]))
print(df)

produce:

  EventType  estado
0    Inicio       0
1     start       1
2     fecha       1
3     fecha       1
4      stop       0
5     fecha       0
6     fecha       0
7     start       1
8     fecha       1
9       fin       1

Eventos en orden inverso

Si los eventos vienen en orden inverso (el primero es el más nuevo), vamos a invertir la columna antes de formar la secuencia. Una vez formada la secuencia, la invierto y la agrego como columna al dataframe original.

import pandas as pd

data = ["Inicio", "start", "fecha", "fecha",  "stop", "fecha", "fecha", "start", "fecha", "fin"]

df =  pd.DataFrame(data, columns=["EventType"])
opciones = dict(stop=0, start=1, last=0)
def value(texto):
    if texto in opciones:
        opciones['last'] = opciones[texto]
    return opciones['last']
            
events = df["EventType"].values[::-1]
df["estado"] = list(pd.Series(list(map(value, events))))[::-1]
print(df)

produce:

  EventType  estado
0    Inicio       1
1     start       1
2     fecha       0
3     fecha       0
4      stop       0
5     fecha       1
6     fecha       1
7     start       1
8     fecha       0
9       fin       0

Observaciones

Antes de invocar value() para un nuevo dataframe, hay que reinicializar opciones['last'] = 0

Answered by Candid Moe on November 18, 2021

El problema de los DataFrames:

De manera nativa si puedes generar una colección de datos en base a los elementos de otra, lo que pasa es que la columna de un DataFrame no es una colección de datos nativa del lenguaje. Si embargo se pueden extraer datos de un DataFrame como una colección de datos nativa.

¿hay algún modo de hacerlo sin usar un bucle for?

Existe la función implementada map():

Return an iterator that applies function to every item of iterable, yielding the results. If additional iterable arguments are passed, function must take that many arguments and is applied to the items from all iterables in parallel.

¿Cómo se hace?

El DataFrame que se tiene

En este caso voy a usar un diccionario como manera de creación del DataFrame, de n * 1 de dimensión. Los valores de la única columna provienen de esta secuencia:

secuencia = ["Inicio",None,None,None,"Final",None,None,None,"Inicio",None,None,None,None,"Indefinido",None,"Final",None,0,"Inicio","Final","Inicio",0,None,0,"Final"]

Donde:

  • "Inicio" representa el inicio de un evento
  • "Final" representa el final de un evento
  • Cualquier otro valor representa la longitud del evento

Como dije, si se genera un DataFrameasí

df = pd.DataFrame({"EventType": secuencia})

se obtiene una columna con varias filas

     EventType
0       Inicio
1         None
2         None
3         None
4        Final
5         None
6         None
7         None
8       Inicio
9         None
10        None
11        None
12        None
13  Indefinido
14        None
15       Final
16        None
17           0
18      Inicio
19       Final
20      Inicio
21           0
22        None
23           0
24       Final

Extrayendo la columna del DataFrame

Por suerte, podemos acceder a los valores de una columna en específico al igual de comos nos referimos a los valores de una llave en un diccionario. Por medio de el atributo values y la función list() se puede obtener en formato de lista los valores de la columna en cuestión. Por lo tanto, se pueden almacenar dichos valores en tipo de dato nativo.

EventType = list(df["EventType"].values)

se obtiene

['Inicio', None, None, None, 'Final', None, None, None, 'Inicio', None, None, None, None, 'Indefinido', None, 'Final', None, 0, 'Inicio', 'Final', 'Inicio', 0, None, 0, 'Final']

Generando la nueva columna del DataFrame

Según la especificación de map()

map(function, iterable, ...)

Requiere de una función y un iterable

  • La función

Bien puede ser definida o una función anónima, hay que decir que es más conveniente usar una función lambda anónima. Esto ya que de esa manera al función quedará como una expresión, su tiempo de vida es solo el necesario.

Es necesario que tenga un parámetro, para que itere de uno en uno y ejecute las instrucciones con base a un elemento. Si se usaran más argumentos la cantidad de elementos que se usan por ciclo sería equivalente a la cantidad de argumentos.

  • El iterable

Si queremos generar otra lista en base a los elementos de EventType, es lo que vamos a usar. Se debe de saber cuando empieza o termina un evento. Por la tanto no hay más remedio que definir una función (con una variable de ámbito global):

x = None

def event(n):
    global x

    if n == "Inicio":
        x = 1
    elif n == "Final":
        x = 0
    else:
        x = x

    return x

De esta manera puede perdurar el valor de x después de cada iteración que hace map(). La lista se tendría que generar con una lambda de un argumento, para que de esta forma todos los elementos de secuencia pasen por la función event()

state = list(map(lambda n: event(n),secuencia))

Esta expresión, significa añadir un elemento a State por cada elemento en EventType. Este elemento será el retornado por event().

Se estaría obteniendo esto

[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0]

Obteniendo el DataFrame final:

Se podría montar otro DataFrame nuevo desde cero, pero es mejor reutilizar el actual y añadir una serie como columna:

df["State"] = pd.Series(state)

A la hora de imprimir por pantalla con print(df) se obtiene

     EventType  State
0       Inicio      1
1         None      1
2         None      1
3         None      1
4        Final      0
5         None      0
6         None      0
7         None      0
8       Inicio      1
9         None      1
10        None      1
11        None      1
12        None      1
13  Indefinido      1
14        None      1
15       Final      0
16        None      0
17           0      0
18      Inicio      1
19       Final      0
20      Inicio      1
21           0      1
22        None      1
23           0      1
24       Final      0

Espero te haya servido de algo, saludos.

Answered by user166844 on November 18, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP