import pandas as pd
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import plotly.express as px
import numpy as np
import plotly.io as pio
import plotly
import os
df = pd.read_csv('formatted_data.csv') # dernière mise à jour le 12 avril 2022 à 16h00.
dfHistPivot = None
if os.path.exists('./dfHistPivot.csv'):
dfHistPivot = pd.read_csv('dfHistPivot.csv')
dfHistPivot.set_index
global counter
def modeEntity(x):
if x.mode().shape[0] > 1:
global counter
counter +=1
return x.iloc[-1]
else:
return x.mode().iat[0]
df = df[df['fiscYear'].apply(lambda x : x >= 2015)]
dfPCQ = df[df['entity'] == 'P.C.Q./C.P.Q.']
dfMP = df[df['entity'].isin(['C.A.Q.- É.F.L.', 'P.C.Q./C.P.Q.','P.L.Q./Q.L.P.', 'P.Q.', 'Q.S.'])]
df = df[['firstName', 'lastName', 'city', 'postalCode', 'entity', 'amount', 'fiscYear']]
# Une personne unique s'identifie par son prenom, nom, ville et code postal
idPerson = ['firstName', 'lastName', 'city', 'postalCode']
#trouvons le parti pour lequel une personne a donnée le plus de fois dans une année et ce, pour chaque année
if dfHistPivot is None:
#takes 10-30 sec
dfHistPivot = dfMP.pivot_table(index=idPerson, columns='fiscYear', values='entity',aggfunc=lambda x: x.mode().iat[0])
counter = 0
dfHistPivot = dfMP.pivot_table(index=idPerson, columns='fiscYear', values='entity',aggfunc=modeEntity)
pourc_odd_donation_in_one_year = counter / (dfHistPivot.shape[0] * dfHistPivot.shape[1]) * 100
dfHistPivot.to_csv('dfHistPivot.csv')
else:
dfHistPivot.set_index(idPerson, inplace = True)
dfHistPivot.rename(columns=lambda x : float(x), inplace = True)
def nouveau(dons:list[str])->bool:
"""Regarde si toutes les donations sont de 2019 a 2022.
si oui, retourne True, si non, retourne False"""
cycle1AllNull = dons.loc[2015:2018].isna().values.all() == True # toutes les valeurs sont nulles dans le premier cycle.
cycle2NotAllNull = dons.loc[2019:2022].isna().values.all() != True # Il y a au moins une valeur qui n'est pas null dans le deuxieme cycle
if cycle1AllNull and cycle2NotAllNull:
return True
else:
return False
def fidele_fragile_volatile(dons:list[str], nouveau:bool):
"""Identifie un donateur fidele, fragile ou volatile:
un donateur fidele a donnée 100% au même parti et plus d'une fois.
un donateur fragile donne 1 fois seulement
un donateur volatile donne à plusieurs partis
"""
entity = dons.loc[2015:2022].dropna().iloc[-1]
# cas nouveau
if nouveau:
allSameEntity = np.all(dons.loc[2019:2022].dropna() == entity)
n_dons = dons.loc[2019:2022].dropna().shape[0]
# cas fidele
if allSameEntity and n_dons > 1:
return 'fidele'
# cas fragile
elif n_dons == 1:
return 'unique'
# cas volatile
else:
return 'volatile'
# cas ancien
else:
allSameEntity = np.all(dons.loc[2015:2022].dropna() == entity)
n_dons = dons.loc[2015:2022].dropna().shape[0]
# cas fidele
if allSameEntity and n_dons > 1:
return 'fidele'
# cas fragile
elif n_dons == 1:
return 'unique'
# cas volatile
else:
return 'volatile'
def format_type_label(nouveau:bool, f_f_v:str):
label = ''
if nouveau:
return 'nouveau_' + f_f_v
else:
return 'ancien_' + f_f_v
def ancienEntite(row:list[str]):
entite = row['dernierDon']
if row['type'] == 'volatile':
return row.loc[2015:2022].dropna().iloc[-2]
else:
entite
sumDonsYearMP = pd.pivot_table(dfMP, index=['fiscYear', 'entity'], values=['amount'], aggfunc='sum')
nbrDonsYearMP = pd.pivot_table(dfMP, index=['fiscYear', 'entity'], values=['amount'], aggfunc='count')
meanDonsYearMP = pd.pivot_table(dfMP, index=['fiscYear', 'entity'], values=['amount'], aggfunc='mean')
modeDonsYearMP = pd.pivot_table(dfMP, index=['fiscYear', 'entity'], values=['amount'], aggfunc=pd.Series.mode)
pivotDonsYearMP = pd.pivot_table(dfMP, index=['entity', 'fiscYear'], values=['amount'], aggfunc=['count', 'sum', 'mean'])
dfPCQ['fiscYear'] = dfPCQ['fiscYear'].astype(str)
nbrDonsCarte = dfPCQ.groupby(['latitude', 'longitude', 'city', 'fiscYear']).count()['amount']
sumDonsCarte = dfPCQ.groupby(['latitude', 'longitude', 'city', 'fiscYear']).sum()['amount']
dfHistPivot['nouveau_ancien'] = dfHistPivot.apply(lambda x : nouveau(x), axis=1)
dfHistPivot['type'] = dfHistPivot.apply(lambda x : fidele_fragile_volatile(x.loc[2015:2022], x.loc['nouveau_ancien']), axis=1)
dfHistPivot['label_type'] = dfHistPivot.loc[:,['nouveau_ancien','type']].apply(lambda x : format_type_label(x.loc['nouveau_ancien'], x.loc['type']), axis=1)
dfHistPivot['dernierDon'] = dfHistPivot.loc[:,2015.0:2022.0].apply(lambda x : x.dropna().iloc[-1], axis=1)
dfHistPivot['ancienEntite'] = dfHistPivot.loc[:,:].apply(lambda x : ancienEntite(x), axis=1)
typeDonateurs = dfHistPivot.groupby(['dernierDon', 'label_type']).size()
typeDonateurs.name = 'nbrType'
typeDonateurs = typeDonateurs.reset_index()
typeDonateurs['pourcentageType'] = typeDonateurs.apply(lambda row : row.loc['nbrType'] *100/typeDonateurs[typeDonateurs['dernierDon'] == row.loc['dernierDon']]['nbrType'].sum(), axis=1)
typeDonateurRaw = dfHistPivot.groupby(['dernierDon', 'nouveau_ancien', 'type']).size()
typeDonateurRaw.name = 'nbrType'
typeDonateurRaw = typeDonateurRaw.reset_index()
typeDonateurRaw = typeDonateurRaw.replace({True:'Nouveau', False:'Ancien'})
typeDonateurRaw['pourcentageType'] = typeDonateurs.loc[:,'pourcentageType']
typeDonateurRaw.to_csv('typeDonateur.csv', index=False)
sumDonsYearMP = pd.pivot_table(dfMP, index=['entity', 'fiscYear'], values=['amount'], aggfunc='sum')
nbrDonsYearMP = pd.pivot_table(dfMP, index=['entity', 'fiscYear'], values=['amount'], aggfunc='count')
meanDonsYearMP = pd.pivot_table(dfMP, index=['entity', 'fiscYear'], values=['amount'], aggfunc='mean')
modeDonsYearMP = pd.pivot_table(dfMP, index=['entity', 'fiscYear'], values=['amount'], aggfunc=pd.Series.mode)
pivotDonsYearMP = pd.pivot_table(dfMP, index=['entity', 'fiscYear'], values=['amount'], aggfunc=['count', 'sum', 'mean'])
dfPCQ['fiscYear'] = dfPCQ['fiscYear'].astype(str)
nbrDonsCarte = dfPCQ.groupby(['latitude', 'longitude', 'city', 'fiscYear']).count()['amount']
sumDonsCarte = dfPCQ.groupby(['latitude', 'longitude', 'city', 'fiscYear']).sum()['amount']
(231163, 7)
Les données proviennent du site d'élection Québec. Elles ont été extraitent grâce à un code développer pour ce projet répertoire Github.
La source de données permet d'obtenir de l'information sur tous les donateurs depuis l'années 2000. Toutefois, nous avons accès au code postal et à la ville pour tous les donateurs depuis 2013 exclusivement. Pour cette raison, nous avons limité notre analyse de 2014 au 17 mars 2022. Une dernière mise à jour des données sera faite avant la publication.
Pour chaque dons, nous avons accès aux informations suivantes :
firstName | lastName | amount | nbrPayment | entity | fiscYear | postalCode | city |
---|---|---|---|---|---|---|---|
Émilie | A Lachance | 100.0 | 1 | P.L.Q./Q.L.P. | 2016 | H2S2C5 | Montréal |
Félix | A-Papineau | 100.0 | 1 | P.L.Q./Q.L.P. | 2015 | J0W1C0 | Ferme-neuve |
Miriam | Aaron | 100.0 | 1 | P.L.Q./Q.L.P. | 2013 | H3G1L2 | Montréal |
Miriam | Aaron | 50.0 | 1 | P.L.Q./Q.L.P. | 2014 | H3G1L2 | Montréal |
... | ... | ... | ... | ..... | ... | ... | ... |
firstName | lastName | city | postalCode | 2015.0 | 2016.0 | 2017.0 | 2018.0 | 2019.0 | 2020.0 | 2021.0 | 2022.0 |
---|---|---|---|---|---|---|---|---|---|---|---|
Alain | Beaulieu | Lefebvre | J0H 2C0 | P.C.Q./C.P.Q. | |||||||
Alain | Mattard | Montréal | H1R 3A3 | P.C.Q./C.P.Q. | P.C.Q./C.P.Q. | P.C.Q./C.P.Q. | |||||
Alain | Rochon | Cayamant | J0X 1Y0 | Q.S. | P.C.Q./C.P.Q. | P.C.Q./C.P.Q. | |||||
Alan | Wallis | Saint-denis-de-brompton | J0B 2P0 | C.A.Q.- É.F.L. | C.A.Q.- É.F.L. | C.A.Q.- É.F.L. | C.A.Q.- É.F.L. | P.C.Q./C.P.Q. |
Une fois que l'on pivote les données sur le prenom, nom, code postal et ville, il nous est possible d'avoir accès à tous les donateurs distincts et aux partis auquels ils ont fait le plus de dons dans une année.
Par exemple, si un individu fait 2 dons pour le PQ en 2017 et 1 pour le PLQ, la colonne sera PQ pour ce donateur en 2017.
À égalité, le don effectué au dernier parti est conservé. Par exemple, si une personne donne pour PQ,PLQ,PQ,PLQ en 2018, la valeur PLQ sera inscrite dans la colonne 2018 pour ce donateur.
Le dernier scénario s'est produite 1851 fois sur les 873960 valeurs du tableau. Considérant que nous observons 109245 donateurs unique sur 8 années différentes, il s'agit d'un scénario s'étant produit 0.2% de fois. Il ne s'agit donc pas d'une limite importante et n'influencera que très peu les conclusions de cette analyse.
Avec notre analyse, on aimerait pouvoir détecter si les partis ont attiré des nouveaux donateurs le début du dernier cycle électoral (2019), ou bien s'il s'agissait de donateur qui avait déjà l'habitude de donner au dernier cycle [2015-2018].
On aimerait pouvoir assigner un donateur à un parti X, pour se faire, on regarde le dernier don qu'il a fait et on l'assigne à ce parti.
Ensuite, on détermine s'il s'agit d'un nouveau ou d'un ancien donateur avec les définitions suivantes:
Nouveau donateur : Donateur n'ayant pas donné entre 2015 et 2018 inclusivement et ayant donné au moins une fois entre 2019 et 2022
Ancien donateur : Quelqu'un qui n'est pas un nouveau donateur à nécessairement donné entre 2015 et 2018 ce qui en fait un ancien. Pour les donateurs ayant données au parti X en dernier, on s'intéresse au profil de donateur pour ce parti:
On aimerait également savoir s'il s'agit de personne qui donne fidèle, s'il s'agit d'une personne ayant fait un don unique ou ayant des habidudes de dons variés. On définit pour les nouveaux et les anciens une spécification supplémentaire :
Donateur fidèle: Un donateur ayant donné plus d'un don et tous les dons sont pour le même parti.
Donateurs unique : Un donateur ayant donné qu'une seule fois.
Donateurs volatile : Il s'agit d'un donateur qui n'est pas fièle ou pas unique. On peut imaginer quelqu'un qui a donné plus d'une fois, mais pas toujours pour le même parti entre 2015 et 2022 (Donateur ancien et volatile) ou entre 2019 et 2022 (Donateur nouveau et volatile).
Il y a 109245 donateurs distincts entre 2015 et 2022
dfHistPivot.shape[0]
109245
(
dfHistPivot.nouveau_ancien.value_counts()
.rename(index={False:'Ancien', True:'Nouveau'})
)
Ancien 72562 Nouveau 36683 Name: nouveau_ancien, dtype: int64
typeDonateurRaw.groupby(['dernierDon', 'nouveau_ancien']).sum()
nbrType | pourcentageType | ||
---|---|---|---|
dernierDon | nouveau_ancien | ||
C.A.Q.- É.F.L. | Ancien | 12020 | 51.565852 |
Nouveau | 11290 | 48.434148 | |
P.C.Q./C.P.Q. | Ancien | 691 | 5.174480 |
Nouveau | 12663 | 94.825520 | |
P.L.Q./Q.L.P. | Ancien | 20870 | 84.333455 |
Nouveau | 3877 | 15.666545 | |
P.Q. | Ancien | 28417 | 86.305655 |
Nouveau | 4509 | 13.694345 | |
Q.S. | Ancien | 10564 | 70.861283 |
Nouveau | 4344 | 29.138717 |
plotly.offline.init_notebook_mode()
px.bar(typeDonateurRaw.groupby(['dernierDon', 'nouveau_ancien']).sum().reset_index(),
x='dernierDon',
y='pourcentageType',
color='nouveau_ancien',
labels= {'dernierDon': 'Parti', 'nbrType':'Nombre de donateur distinct'},
color_discrete_sequence=["Black", "Green"],
title = "Pourcentage de nouveau donateur et d'ancien donateur pour chaque parti",
hover_data=['nbrType', 'dernierDon'],
barmode='group'
)