tutorial tablas seleccionar recorrer para leer graficar funciones filas espaƱol documentacion datos data con columnas python pandas sorting dataframe grouping

python - tablas - seleccionar columnas de un dataframe pandas



Reagrupar valores de columna en una pandas df (4)

intento actual

A continuación, he agregado algunas líneas antes de las últimas líneas de su código:

d = ({''Time'': [''8:03:00'', ''8:17:00'', ''8:20:00'', ''10:15:00'', ''10:15:00'', ''11:48:00'', ''12:00:00'', ''12:10:00''], ''Place'': [''House 1'', ''House 2'', ''House 1'', ''House 3'', ''House 4'', ''House 5'', ''House 1'', ''House 1''], ''Area'': [''X'', ''X'', ''Y'', ''X'', ''X'', ''X'', ''X'', ''X'']}) df = pd.DataFrame(data=d) def g(gps): s = gps[''Place''].unique() d = dict(zip(s, np.arange(len(s)) // 3 + 1)) gps[''Person''] = gps[''Place''].map(d) return gps df = df.groupby(''Area'', sort=False).apply(g) s = df[''Person''].astype(str) + df[''Area''] # added lines t = s.value_counts() df_sub = df.loc[s[s.isin(t[t < 3].index)].index].copy() df_sub["tag"] = df_sub["Place"] + df_sub["Area"] tags = list(df_sub.tag.unique()) f = lambda x: f''R{int(tags.index(x) / 3) + 1}'' df_sub[''reassign''] = df_sub.tag.apply(f) s[s.isin(t[t < 3].index)] = df_sub[''reassign''] df[''Person''] = pd.Series(pd.factorize(s)[0] + 1).map(str).radd(''Person '')

Para ser honesto, no estoy tan seguro de que funcione en todos los casos, pero proporciona el resultado deseado en el caso de prueba.

Intentos anteriores

Veamos si puedo ayudarlo con una comprensión limitada de lo que intenta hacer.

Tiene datos secuenciales (los llamaré eventos) y desea asignar a cada evento un identificador de "persona". El identificador que asignará en cada suceso sucesivo depende de las asignaciones anteriores y me parece que debe regirse por las siguientes reglas que se deben aplicar de forma secuencial:

  1. Te conozco : puedo reutilizar un identificador anterior si: ya aparecieron los mismos valores para "Lugar" y "Área" para un identificador dado (¿ tiene tiempo algo que ver con eso? ).

  2. NO te conozco : crearé un nuevo identificador si: aparece un nuevo valor de Área (¿ así que Lugar y Área desempeñan roles diferentes? ).

  3. ¿te conozco? : Podría reutilizar un identificador utilizado anteriormente si: no se ha asignado un identificador a al menos tres eventos ( ¿qué sucede si esto ocurre con varios identificadores? Asumiré que uso el más antiguo ...).

  4. nah, no lo hago : en caso de que ninguna de las reglas anteriores se aplique, crearé un nuevo identificador.

Habiendo asumido lo anterior, lo siguiente es una implementación de una solución:

# dict of list of past events assigned to each person. key is person identifier people = dict() # new column for df (as list) it will be appended at the end to dataframe persons = list() # first we define the rules def i_know_you(people, now): def conditions(now, past): return [e for e in past if (now.Place == e.Place) and (now.Area == e.Area)] i_do = [person for person, past in people.items() if conditions(now, past)] if i_do: return i_do[0] return False def i_do_not_know_you(people, now): conditions = not bool([e for past in people.values() for e in past if e.Area == now.Area]) if conditions: return f''Person {len(people) + 1}'' return False def do_i_know_you(people, now): i_do = [person for person, past in people.items() if len(past) < 3] if i_do: return i_do[0] return False # then we process the sequential data for event in df.itertuples(): print(''event:'', event) for rule in [i_know_you, i_do_not_know_you, do_i_know_you]: person = rule(people, event) print(''/t'', rule.__name__, person) if person: break if not person: person = f''Person {len(people) + 1}'' print(''/t'', "nah, I don''t", person) if person in people: people[person].append(event) else: people[person] = [event] persons.append(person) df[''Person''] = persons

Salida:

event: Pandas(Index=0, Time=''8:00:00'', Place=''House 1'', Area=''X'', Person=''Person 1'') i_know_you False i_do_not_know_you Person 1 event: Pandas(Index=1, Time=''8:30:00'', Place=''House 2'', Area=''X'', Person=''Person 1'') i_know_you False i_do_not_know_you False do_i_know_you Person 1 event: Pandas(Index=2, Time=''9:00:00'', Place=''House 1'', Area=''Y'', Person=''Person 2'') i_know_you False i_do_not_know_you Person 2 event: Pandas(Index=3, Time=''9:30:00'', Place=''House 3'', Area=''X'', Person=''Person 1'') i_know_you False i_do_not_know_you False do_i_know_you Person 1 event: Pandas(Index=4, Time=''10:00:00'', Place=''House 4'', Area=''X'', Person=''Person 2'') i_know_you False i_do_not_know_you False do_i_know_you Person 2 event: Pandas(Index=5, Time=''10:30:00'', Place=''House 5'', Area=''X'', Person=''Person 2'') i_know_you False i_do_not_know_you False do_i_know_you Person 2 event: Pandas(Index=6, Time=''11:00:00'', Place=''House 1'', Area=''X'', Person=''Person 1'') i_know_you Person 1 event: Pandas(Index=7, Time=''11:30:00'', Place=''House 6'', Area=''X'', Person=''Person 3'') i_know_you False i_do_not_know_you False do_i_know_you False nah, I don''t Person 3 event: Pandas(Index=8, Time=''12:00:00'', Place=''House 7'', Area=''X'', Person=''Person 3'') i_know_you False i_do_not_know_you False do_i_know_you Person 3 event: Pandas(Index=9, Time=''12:30:00'', Place=''House 8'', Area=''X'', Person=''Person 3'') i_know_you False i_do_not_know_you False do_i_know_you Person 3

y el último marco de datos es, como quieras:

Time Place Area Person 0 8:00:00 House 1 X Person 1 1 8:30:00 House 2 X Person 1 2 9:00:00 House 1 Y Person 2 3 9:30:00 House 3 X Person 1 4 10:00:00 House 4 X Person 2 5 10:30:00 House 5 X Person 2 6 11:00:00 House 1 X Person 1 7 11:30:00 House 6 X Person 3 8 12:00:00 House 7 X Person 3 9 12:30:00 House 8 X Person 3

Observación : tenga en cuenta que evité intencionalmente el uso agrupado por operaciones y procesé datos de forma secuencial. Creo que este tipo de complejidad ( y no entender realmente lo que quieres hacer ... ) requiere ese enfoque. Además, puede adaptar las reglas para que sean más complicadas ( ¿el tiempo realmente juega un papel o no? ) Utilizando la misma estructura anterior.

Respuesta actualizada para nuevos datos.

Al observar los nuevos datos es evidente que no entendí lo que está tratando de hacer (en particular, la asignación no parece seguir reglas secuenciales ). Tendría una solución que funcionaría en su segundo conjunto de datos, pero daría un resultado diferente para el primer conjunto de datos.

La solución es mucho más simple y agregará una columna (que puede soltar más adelante si lo desea):

df["tag"] = df["Place"] + df["Area"] tags = list(df.tag.unique()) f = lambda x: f''Person {int(tags.index(x) / 3) + 1}'' df[''Person''] = df.tag.apply(f)

En el segundo conjunto de datos, daría:

Time Place Area tag Person 0 8:00:00 House 1 X House 1X Person 1 1 8:30:00 House 2 X House 2X Person 1 2 9:00:00 House 3 X House 3X Person 1 3 9:30:00 House 1 Y House 1Y Person 2 4 10:00:00 House 1 Z House 1Z Person 2 5 10:30:00 House 1 V House 1V Person 2

En el primer conjunto de datos da:

Time Place Area tag Person 0 8:00:00 House 1 X House 1X Person 1 1 8:30:00 House 2 X House 2X Person 1 2 9:00:00 House 1 Y House 1Y Person 1 3 9:30:00 House 3 X House 3X Person 2 4 10:00:00 House 4 X House 4X Person 2 5 10:30:00 House 5 X House 5X Person 2 6 11:00:00 House 1 X House 1X Person 1 7 11:30:00 House 6 X House 6X Person 3 8 12:00:00 House 7 X House 7X Person 3 9 12:30:00 House 8 X House 8X Person 3

Esto es diferente de su salida prevista en el índice 2 y 3. ¿Esta salida está bien con sus requisitos? Por qué no?

Tengo un script que asigna un valor basado en dos columns en una pandas df . El código de abajo es capaz de implementar el primer paso, pero estoy luchando con el segundo.

Así que el guión debería inicialmente:

1) Asigne una Person para cada string individual en [Area] y los primeros 3 unique values en [Place]

2) Busque reasignar People con menos de 3 unique values . Ejemplo. El df continuación tiene 6 unique values en [Area] y [Place] . Pero se asignan 3 People . Idealmente, 2 personas tendrán 2 unique values cada una.

d = ({ ''Time'' : [''8:03:00'',''8:17:00'',''8:20:00'',''10:15:00'',''10:15:00'',''11:48:00'',''12:00:00'',''12:10:00''], ''Place'' : [''House 1'',''House 2'',''House 1'',''House 3'',''House 4'',''House 5'',''House 1'',''House 1''], ''Area'' : [''X'',''X'',''Y'',''X'',''X'',''X'',''X'',''X''], }) df = pd.DataFrame(data=d) def g(gps): s = gps[''Place''].unique() d = dict(zip(s, np.arange(len(s)) // 3 + 1)) gps[''Person''] = gps[''Place''].map(d) return gps df = df.groupby(''Area'', sort=False).apply(g) s = df[''Person''].astype(str) + df[''Area''] df[''Person''] = pd.Series(pd.factorize(s)[0] + 1).map(str).radd(''Person '')

Salida:

Time Place Area Person 0 8:03:00 House 1 X Person 1 1 8:17:00 House 2 X Person 1 2 8:20:00 House 1 Y Person 2 3 10:15:00 House 3 X Person 1 4 10:15:00 House 4 X Person 3 5 11:48:00 House 5 X Person 3 6 12:00:00 House 1 X Person 1 7 12:10:00 House 1 X Person 1

Como puedes ver, el primer paso funciona bien. o cada string individual en [Area] , los primeros 3 unique values en [Place] se asignan a una Person . Esto deja a la Person 1 con 3 values , la Person 2 con 1 value y la Person 3 con 2 values .

El segundo paso es donde estoy luchando.

Si una Person tiene menos de 3 unique values asignados, modifíquelo para que cada Person tenga hasta 3 unique values

Salida prevista:

Time Place Area Person 0 8:03:00 House 1 X Person 1 1 8:17:00 House 2 X Person 1 2 8:20:00 House 1 Y Person 2 3 10:15:00 House 3 X Person 1 4 10:15:00 House 4 X Person 2 5 11:48:00 House 5 X Person 2 6 12:00:00 House 1 X Person 1 7 12:10:00 House 1 X Person 1

Descripción:

Person 1 ya tenía 3 unique values asignados a todo lo bueno. Person 2 y 3 tenían menos, así que deberíamos buscar combinar estos. Todos los valores duplicados deben seguir siendo los mismos.


¿Qué tal esto para el paso 2:

def reduce_df(df): values = df[''Area''] + df[''Place''] df1 = df.loc[~values.duplicated(),:] # ignore duplicate values for this part.. person_count = df1.groupby(''Person'')[''Person''].agg(''count'') leftover_count = person_count[person_count < 3] # the ''leftovers'' # try merging pairs together nleft = leftover_count.shape[0] to_try = np.arange(nleft - 1) to_merge = (leftover_count.values[to_try] + leftover_count.values[to_try + 1]) <= 3 to_merge[1:] = to_merge[1:] & ~to_merge[:-1] to_merge = to_try[to_merge] merge_dict = dict(zip(leftover_count.index.values[to_merge+1], leftover_count.index.values[to_merge])) def change_person(p): if p in merge_dict.keys(): return merge_dict[p] return p reduced_df = df.copy() # update df with the merges you found reduced_df[''Person''] = reduced_df[''Person''].apply(change_person) return reduced_df print( reduce_df(reduce_df(df)) # call twice in case 1,1,1 -> 2,1 -> 3 )

La salida:

Area Place Time Person 0 X House 1 8:03:00 Person 1 1 X House 2 8:17:00 Person 1 2 Y House 1 8:20:00 Person 2 3 X House 3 10:15:00 Person 1 4 X House 4 10:15:00 Person 2 5 X House 5 11:48:00 Person 2 6 X House 1 12:00:00 Person 1 7 X House 1 12:10:00 Person 1


En primer lugar, esta respuesta no cumple con su requisito de reasignar únicamente las sobras (por lo que no espero que las acepte). Dicho esto, lo estoy publicando de todos modos porque su restricción de ventana de tiempo fue difícil de resolver dentro de un mundo pandas. Quizás mi solución no sea útil para usted en este momento, pero tal vez más adelante;) Al menos fue una experiencia de aprendizaje para mí, por lo que tal vez otros puedan beneficiarse de ella.

import pandas as pd from datetime import datetime, time, timedelta import random # --- helper functions for demo random.seed( 0 ) def makeRandomTimes( nHours = None, mMinutes = None ): nHours = 10 if nHours is None else nHours mMinutes = 3 if mMinutes is None else mMinutes times = [] for _ in range(nHours): hour = random.randint(8,18) for _ in range(mMinutes): minute = random.randint(0,59) times.append( datetime.combine( datetime.today(), time( hour, minute ) ) ) return times def makeDf(): times = makeRandomTimes() houses = [ str(random.randint(1,10)) for _ in range(30) ] areas = [ [''X'',''Y''][random.randint(0,1)] for _ in range(30) ] df = pd.DataFrame( {''Time'' : times, ''House'' : houses, ''Area'' : areas } ) return df.set_index( ''Time'' ).sort_index() # --- real code begins def evaluateLookback( df, idx, dfg ): mask = df.index >= dfg.Lookback.iat[-1] personTotals = df[ mask ].set_index(''Loc'')[''Person''].value_counts() currentPeople = set(df.Person[ df.Person > -1 ]) noAllocations = currentPeople - set(personTotals.index) available = personTotals < 3 if noAllocations or available.sum(): # allocate to first available person person = min( noAllocations.union(personTotals[ available ].index) ) else: # allocate new person person = len( currentPeople ) df.Person.at[ idx ] = person # debug df.Verbose.at[ idx ] = ( noAllocations, available.sum() ) def lambdaProxy( df, colName ): [ dff[1][colName].apply( lambda f: f(df,*dff) ) for dff in df.groupby(df.index) ] lookback = timedelta( minutes = 120 ) df1 = makeDf() df1[ ''Loc'' ] = df1[ ''House'' ] + df1[ ''Area'' ] df1[ ''Person'' ] = None df1[ ''Lambda'' ] = evaluateLookback df1[ ''Lookback'' ] = df1.index - lookback df1[ ''Verbose'' ] = None lambdaProxy( df1, ''Lambda'' ) print( df1[ [ col for col in df1.columns if col != ''Lambda'' ] ] )

Y la salida de muestra en mi máquina se ve así:

House Area Loc Person Lookback Verbose Time 2018-09-30 08:16:00 6 Y 6Y 0 2018-09-30 06:16:00 ({}, 0) 2018-09-30 08:31:00 4 Y 4Y 0 2018-09-30 06:31:00 ({}, 1) 2018-09-30 08:32:00 10 X 10X 0 2018-09-30 06:32:00 ({}, 1) 2018-09-30 09:04:00 4 X 4X 1 2018-09-30 07:04:00 ({}, 0) 2018-09-30 09:46:00 10 X 10X 1 2018-09-30 07:46:00 ({}, 1) 2018-09-30 09:57:00 4 X 4X 1 2018-09-30 07:57:00 ({}, 1) 2018-09-30 10:06:00 1 Y 1Y 2 2018-09-30 08:06:00 ({}, 0) 2018-09-30 10:39:00 10 X 10X 0 2018-09-30 08:39:00 ({0}, 1) 2018-09-30 10:48:00 7 X 7X 0 2018-09-30 08:48:00 ({}, 2) 2018-09-30 11:08:00 1 Y 1Y 0 2018-09-30 09:08:00 ({}, 3) 2018-09-30 11:18:00 2 Y 2Y 1 2018-09-30 09:18:00 ({}, 2) 2018-09-30 11:32:00 9 X 9X 2 2018-09-30 09:32:00 ({}, 1) 2018-09-30 12:22:00 5 Y 5Y 1 2018-09-30 10:22:00 ({}, 2) 2018-09-30 12:30:00 9 X 9X 1 2018-09-30 10:30:00 ({}, 2) 2018-09-30 12:34:00 6 X 6X 2 2018-09-30 10:34:00 ({}, 1) 2018-09-30 12:37:00 1 Y 1Y 2 2018-09-30 10:37:00 ({}, 1) 2018-09-30 12:45:00 4 X 4X 0 2018-09-30 10:45:00 ({}, 1) 2018-09-30 12:58:00 8 X 8X 0 2018-09-30 10:58:00 ({}, 1) 2018-09-30 14:26:00 7 Y 7Y 0 2018-09-30 12:26:00 ({}, 3) 2018-09-30 14:48:00 2 X 2X 0 2018-09-30 12:48:00 ({1, 2}, 1) 2018-09-30 14:50:00 8 X 8X 1 2018-09-30 12:50:00 ({1, 2}, 0) 2018-09-30 14:53:00 8 Y 8Y 1 2018-09-30 12:53:00 ({2}, 1) 2018-09-30 14:56:00 6 X 6X 1 2018-09-30 12:56:00 ({2}, 1) 2018-09-30 14:58:00 9 Y 9Y 2 2018-09-30 12:58:00 ({2}, 0) 2018-09-30 17:09:00 2 Y 2Y 0 2018-09-30 15:09:00 ({0, 1, 2}, 0) 2018-09-30 17:19:00 4 X 4X 0 2018-09-30 15:19:00 ({1, 2}, 1) 2018-09-30 17:57:00 6 Y 6Y 0 2018-09-30 15:57:00 ({1, 2}, 1) 2018-09-30 18:21:00 3 X 3X 1 2018-09-30 16:21:00 ({1, 2}, 0) 2018-09-30 18:30:00 9 X 9X 1 2018-09-30 16:30:00 ({2}, 1) 2018-09-30 18:35:00 8 Y 8Y 1 2018-09-30 16:35:00 ({2}, 1) >>>

Notas:

  • la variable lookback controla la cantidad de tiempo mirando hacia atrás para considerar las ubicaciones asignadas a una persona
  • la columna Lookback muestra el tiempo de corte
  • evaluateLookback se llama repetidamente para cada fila de la tabla, con df siendo el DataFrame completo, idx el índice / etiqueta actual y dfg la fila actual.
  • lambdaProxy controla la llamada de lambdaProxy .
  • el número de ubicaciones por persona se establece en 3 pero eso podría ajustarse según sea necesario
  • los requisitos arbitrariamente complejos para el período de lookback se pueden administrar al tener otra columna de función que primero se evalúa con lambdaProxy y luego ese resultado se almacena y se usa dentro de lambdaProxy

Hay algunos casos interesantes en la salida de demostración: 10:39:00 , 14:48:00 , 17:09:00

Aparte: ¿Sería interesante ver la "columna de funciones" en los pandas, tal vez con capacidad de memorización? Lo ideal es que la columna ''Persona'' tome una función y calcule a petición, ya sea con su propia fila o con alguna vista de ventana variable. ¿Alguien ha visto algo así?


Por lo que entiendo, estás contento con todo antes de la asignación de persona. Así que aquí hay una solución plug and play para "fusionar" Personas con menos de 3 valores únicos, de modo que cada Persona termine con 3 valores únicos excepto el último (obviamente, en función del segundo hasta el último df que publicó ("Salida:") sin Tocando los que ya tienen 3 valores únicos y simplemente fusiona los otros.

EDITAR: Código muy simplificado. De nuevo, tomando su df como entrada:

n = 3 df[''complete''] = df.Person.apply(lambda x: 1 if df.Person.tolist().count(x) == n else 0) df[''num''] = df.Person.str.replace(''Person '','''') df.sort_values(by=[''num'',''complete''],ascending=True,inplace=True) #get all persons that are complete to the top c = 0 person_numbers = [] for x in range(0,999): #Create the numbering [1,1,1,2,2,2,3,3,3,...] with n defining how often a person is ''repeated'' if x % n == 0: c += 1 person_numbers.append(c) df[''Person_new''] = person_numbers[0:len(df)] #Add the numbering to the df df.Person = ''Person '' + df.Person_new.astype(str) #Fill the person column with the new numbering df.drop([''complete'',''Person_new'',''num''],axis=1,inplace=True)