pandas switch-on switch-off

12 réponses
Avatar
carboleum
Bonjour,

Dans mon dataframe, j'ai une colonne "on" qui allume la lumière et une
colonne "off" qui éteint la lumière.

Quelqu'un connais la fonction (si elle existe ?) qui renvoie la colonne
"lumière allumée" ?

on off res
0 0 0 0
1 0 0 0
2 1 0 1
3 0 0 1
4 0 1 0
5 0 0 0
6 0 0 0
7 0 1 0
8 0 0 0
9 1 0 1
10 0 0 1
11 1 0 1
12 0 0 1
13 0 1 0
14 0 1 0
15 0 0 0

Ou une piste ?

Merci

10 réponses

1 2
Avatar
Damien Wyart
* carboleum in fr.comp.lang.python:
Dans mon dataframe, j'ai une colonne "on" qui allume la lumière et une
colonne "off" qui éteint la lumière.
Quelqu'un connais la fonction (si elle existe ?) qui renvoie la
colonne "lumière allumée" ?
on off res
0 0 0 0
1 0 0 0
2 1 0 1
3 0 0 1
4 0 1 0
5 0 0 0
6 0 0 0
7 0 1 0
8 0 0 0
9 1 0 1
10 0 0 1
11 1 0 1
12 0 0 1
13 0 1 0
14 0 1 0
15 0 0 0

Avant de répondre, je pense qu'il manque quelques infos :
- la ligne 10 donne un résultat de 1 alors qu'il n'y a n'y extinction ni
allumage donc je pense que le résultat dépend aussi d'un état initial
(qui n'est pas affiché ici)
- est-on certain que on et off sont mutuellement exclusifs ? S'il
y a une erreur dans les données et que les deux sont Í  1 est-ce que le
résultat doit être 0 ou 1 ?
--
DW
Avatar
carboleum
On 2/04/21 11:34, Damien Wyart wrote:
* carboleum in fr.comp.lang.python:
Dans mon dataframe, j'ai une colonne "on" qui allume la lumière et une
colonne "off" qui éteint la lumière.

Quelqu'un connais la fonction (si elle existe ?) qui renvoie la
colonne "lumière allumée" ?

on off res
0 0 0 0
1 0 0 0
2 1 0 1
3 0 0 1
4 0 1 0
5 0 0 0
6 0 0 0
7 0 1 0
8 0 0 0
9 1 0 1
10 0 0 1
11 1 0 1
12 0 0 1
13 0 1 0
14 0 1 0
15 0 0 0

Avant de répondre, je pense qu'il manque quelques infos :
- la ligne 10 donne un résultat de 1 alors qu'il n'y a n'y extinction ni
allumage donc je pense que le résultat dépend aussi d'un état initial
(qui n'est pas affiché ici)

si, il y a un allumage en 9.
Je me rends compte qu mon énoncé est ambigͼ
On a deux boutons poussoirs (on et off) (le on sert Í  allumer, le off Í 
éteindre la même lumière). Au temps t=0, je fais rien, la lumière est
éteinte. En t=1, idem. En t=2, je pousse sur "on",la lumière s'allume.
En t=3, je fais rien, la lumière reste allumée. En t=4, je pousse sur
"off", la lumière s'éteint. etc.
La difficulté est que je dois reprendre le résultat précédent afin de
déterminer le résultat actuel.
Sinon, un simple boucle peut faire l'affaire:
for i in df.index:
if df['on'][i]:
df['out'][i] = True
elif df['off'][i]:
df['out'][i] = False
elif i == 0:
df['out'][i] = False
else:
df['out'][i] = df['out'][i-1]
mais je trouve ça très peu élégant.
- est-on certain que on et off sont mutuellement exclusifs ? S'il
y a une erreur dans les données et que les deux sont Í  1 est-ce que le
résultat doit être 0 ou 1 ?

Je décide qu'en cas d'allumage et extinction simultané, c'est
l'extinction qui est prioritaire, la lampe s'éteint (1 1 -> 0)
16 0 0 0
17 1 0 1
18 1 1 0
19 0 0 0
Avatar
Damien Wyart
* carboleum in fr.comp.lang.python:
si, il y a un allumage en 9.
Je me rends compte qu mon énoncé est ambigͼ

Merci pour ces précisions, j'avais au départ compris que chaque ligne
était une expérience indépendante.
Sinon, un simple boucle peut faire l'affaire:
for i in df.index:
if df['on'][i]:
df['out'][i] = True
elif df['off'][i]:
df['out'][i] = False
elif i == 0:
df['out'][i] = False
else:
df['out'][i] = df['out'][i-1]
mais je trouve ça très peu élégant.
- est-on certain que on et off sont mutuellement exclusifs ? S'il
y a une erreur dans les données et que les deux sont Í  1 est-ce que le
résultat doit être 0 ou 1 ?

Je décide qu'en cas d'allumage et extinction simultané, c'est
l'extinction qui est prioritaire, la lampe s'éteint (1 1 -> 0)
16 0 0 0
17 1 0 1
18 1 1 0
19 0 0 0

Je vois deux possibilités (donc l'une avec une boucle) :
,----
| #!/usr/bin/python3
|
| import pandas as pd
|
| df = pd.DataFrame([[0, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 0], [0, 0], [0, 1], [0, 0], [1, 0],
| [0, 0], [1, 0], [0, 0], [0, 1], [0, 1], [0, 0], [0, 0], [1, 0], [1, 1], [0, 0]], columns=['on', 'off'])
| df2 = df.copy()
|
| df.at[0, 'res'] = False
|
| for i in range(1, len(df)):
| df.at[i, 'res'] = False if df.at[i, 'off'] else df.at[i, 'on'] or df.at[i-1, 'res']
|
| df['res'] = df['res'].astype(int)
|
| print(df)
| print()
|
| ###
|
| def res(on, off):
| global value
| value = False if off else on or value
| return value
|
| value = False
| df2.at[0, 'res'] = False
|
| df2.loc[1:, 'res'] = df2.loc[1:].apply(lambda row: res(*row[['on', 'off']]), axis=1)
|
| df2['res'] = df2['res'].astype(int)
|
| print(df2)
| print()
`----
Quand la ligne courante ne dépend que de la ligne précédente, on peut
"join" la dataframe avec elle-même en décalant d'une ligne et utiliser
apply, mais ici la ligne courante dépend d'un état qui est plus
complexe, et cela n'est donc pas possible.
--
DW
Avatar
carboleum
On 2/04/21 16:47, Damien Wyart wrote:
Je vois deux possibilités (donc l'une avec une boucle) :
,----
| #!/usr/bin/python3
|
| import pandas as pd
|
| df = pd.DataFrame([[0, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 0], [0, 0], [0, 1], [0, 0], [1, 0],
| [0, 0], [1, 0], [0, 0], [0, 1], [0, 1], [0, 0], [0, 0], [1, 0], [1, 1], [0, 0]], columns=['on', 'off'])
| df2 = df.copy()
|
| df.at[0, 'res'] = False
|
| for i in range(1, len(df)):
| df.at[i, 'res'] = False if df.at[i, 'off'] else df.at[i, 'on'] or df.at[i-1, 'res']
|
| df['res'] = df['res'].astype(int)
|
| print(df)
| print()
|
| ###
|
| def res(on, off):
| global value
| value = False if off else on or value
| return value
|
| value = False
| df2.at[0, 'res'] = False
|
| df2.loc[1:, 'res'] = df2.loc[1:].apply(lambda row: res(*row[['on', 'off']]), axis=1)
|
| df2['res'] = df2['res'].astype(int)
|
| print(df2)
| print()
`----

La solution 2 me plaͮt. C'est vers elle que je voulais aller mais sans y
parvenir. Grand merci :-)
Avatar
Nicolas
|
| def res(on, off):
| global value
| value = False if off else on or value
| return value
|
| value = False
| df2.at[0, 'res'] = False

Une variable globale ?!!!
Brrr... ca fait froid dans le dos.
Que se passera t-il si 2 (ou plus) jeux de données sont Í  traiter ?
Avatar
Damien Wyart
* Nicolas in fr.comp.lang.python:
Que se passera t-il si 2 (ou plus) jeux de données sont Í  traiter ?

C'est vrai que j'aurais pu souligner que cette solution est "quick &
dirty" ; vu le contexte qui semblait plus être un "toy example" qu'autre
chose, cela m'avait semblé intéressant de montrer qu'on pouvait éviter
la boucle "pour le fun".
Pour des jeux de données multiples, on pourrait Í  la limite
(techniquement ça peut fonctionner mais évidemment c'est catastrophique
d'un point de vue design) imaginer une variable globale d'un autre type
Í  valeurs multiples, qui stockerait les différents états (tout ça pour
dire qu'une variable globale n'empêche pas stricto sendu que ça
fonctionne), mais clairement cela n'aurait plus aucun intérêt par
rapport Í  un stockage de l'état dans la dataframe elle-même.
Merci pour votre remarque donc :)
--
DW
Avatar
Alain Ketterlin
Damien Wyart writes:
* Nicolas in fr.comp.lang.python:
Que se passera t-il si 2 (ou plus) jeux de données sont Í  traiter ?

C'est vrai que j'aurais pu souligner que cette solution est "quick &
dirty" ; vu le contexte qui semblait plus être un "toy example" qu'autre
chose, cela m'avait semblé intéressant de montrer qu'on pouvait éviter
la boucle "pour le fun".

[...]
Note que le boucle est cachée dans apply...
A tout hasard, en Python cru (ou presque), ça s'écrirait :
| import functools
|
| df = [[0, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 0], [0, 0], [0, 1],
| [0, 0], [1, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 1], [0, 0],
| [0, 0], [1, 0], [1, 1], [0, 0]]
|
| def res (acc, c):
| return False if c[1] else c[0] or acc
|
| r = functools.reduce (res, df)
J'imagine que pandas doit fournir un truc similaire (ça s'appelle
fold_left ou reduce traditionnellement, mais comme pandas semble appeler
"apply" quelque chose qui s'appelle "map" en général, je m'abstiens de
deviner ; il y a de bonnes raisons pour l'usage d'autres noms, cela dit).
Si tu veux vraiment conserver tous les résultats intermédiaires, c'est
un peu plus sport :
| def reslist (accl, c):
| lst, state = accl
| r = res (state, c)
| return (lst + [c + [r]],r)
|
| rl = functools.reduce (reslist, df, ([],False))
| print (rl[0])
C'est succinct mais ce n'est pas efficace Í  cause des listes de Python
(c'est fait pour des listes fonctionnelles, cÍ d chaÍ®nées, pas pour des
"vecteurs" comme les listes standard). L'appel de reduce construit une
nouvelle liste. Si tu cherches la performance, il vaut mieux écrire la
boucle explicitement, et utiliser append() ou modifier les éléments au
passage (comme dans ta première solution).
-- Alain.
Avatar
Nicolas
Le 06/04/2021 Í  18:42, Damien Wyart a écrit :
* Nicolas in fr.comp.lang.python:
Que se passera t-il si 2 (ou plus) jeux de données sont Í  traiter ?

C'est vrai que j'aurais pu souligner que cette solution est "quick &
dirty" ; vu le contexte qui semblait plus être un "toy example" qu'autre
chose, cela m'avait semblé intéressant de montrer qu'on pouvait éviter
la boucle "pour le fun".
Pour des jeux de données multiples, on pourrait Í  la limite
(techniquement ça peut fonctionner mais évidemment c'est catastrophique
d'un point de vue design) imaginer une variable globale d'un autre type
Í  valeurs multiples, qui stockerait les différents états (tout ça pour
dire qu'une variable globale n'empêche pas stricto sendu que ça
fonctionne), mais clairement cela n'aurait plus aucun intérêt par
rapport Í  un stockage de l'état dans la dataframe elle-même.

Ou tout simplement une petite classe :
class Lumiere() :
def __init__(self, etat_initialúlse) :
self.etat = etat_initial
def OnOff(self, on, off) :
self.etat = False if off else on or self.etat
return self.etat
On peut ajouter ces méthodes si besoin :
def On(self) :
self.etat = True
def Off(self) :
self.etat = False
def Etat(self) :
return self.etat
C'est simple, clair et on peut en instancier autant qu'on veut.
Concernant la boucle, je ne vois pas de problème Í  en instancier une.
Je préfère même une boucle, ça a le mérite d'être explicite. La clarté
du code, c'est primordial.
Après, s'il y a besoin d'une optimisation pour des questions de
performance, c'est autre chose. Mais cela doit être documenté.
Merci pour votre remarque donc :)

Tout le plaisir est pour moi :)
Avatar
carboleum
On 2/04/21 11:19, carboleum wrote:
Bonjour,
Dans mon dataframe, j'ai une colonne "on" qui allume la lumière et une
colonne "off" qui éteint la lumière.
Quelqu'un connais la fonction (si elle existe ?) qui renvoie la colonne
"lumière allumée" ?
    on  off  res
0    0    0    0
1    0    0    0
2    1    0    1
3    0    0    1
4    0    1    0
5    0    0    0
6    0    0    0
7    0    1    0
8    0    0    0
9    1    0    1
10   0    0    1
11   1    0    1
12   0    0    1
13   0    1    0
14   0    1    0
15   0    0    0
Ou une piste ?
Merci

J'ai trouvé o/ Yeah!...
C'est un peu tiré par les cheveux, mais ca me plait! :-)
df = pd.DataFrame([[0, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 0], [0,
0], [0, 1], [0, 0], [1, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 1], [0,
0], [0, 0], [1, 0], [1, 1], [0, 0]], columns=['on', 'off']).astype(bool)
df['sig'] = df.on.where(df.on).fillna(1 - df.off.where(df.off)).ffill()
df
on off sig
0 0 0 NaN
1 0 0 NaN
2 1 0 1.0
3 0 0 1.0
4 0 1 0.0
5 0 0 0.0
6 0 0 0.0
7 0 1 0.0
8 0 0 0.0
9 1 0 1.0
10 0 0 1.0
11 1 0 1.0
12 0 0 1.0
13 0 1 0.0
14 0 1 0.0
15 0 0 0.0
16 0 0 0.0
17 1 0 1.0
18 1 1 1.0
19 0 0 1.0
Avatar
Damien Wyart
* carboleum in fr.comp.lang.python:
J'ai trouvé o/ Yeah!...

:-D
C'est un peu tiré par les cheveux, mais ca me plait! :-)
df = pd.DataFrame([[0, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 0], [0,
0], [0, 1], [0, 0], [1, 0], [0, 0], [1, 0], [0, 0], [0, 1], [0, 1],
[0, 0], [0, 0], [1, 0], [1, 1], [0, 0]], columns=['on',
'off']).astype(bool)
df['sig'] = df.on.where(df.on).fillna(1 - df.off.where(df.off)).ffill()

Pas mal du tout ;-)
Par contre ça ne respecte pas la condition que tu avais ajoutée :
Je décide qu'en cas d'allumage et extinction simultané, c'est l'extinction qui
est prioritaire, la lampe s'éteint (1 1 -> 0)

Je propose dont la variante suivante :
df['res'] = (1 - df.off.where(df.off)).fillna(df.on.where(df.on)).ffill()
--
DW
1 2