<jupyter_start><jupyter_text>Obtention des résultats bruts d'une élection au GC - Cleaning - Regroupement des listes par groupes parlementaires - Estimation électorat partisanLe résultat est exporté dans un fichier Excel local (par ex. _2017-resultats-communes.xlsx_)<jupyter_code>import pandas as pd
import requests
## PARAMETERS

# urls like https://votel.vs.ch/sites/votel2/FR/1/excel/gc/137/commune/csv/1 -> le dernier chiffre = numéro du district
url_base = 'https://votel.vs.ch/sites/votel2/FR/1/excel/gc/137/commune/csv/'
rawresultfile_base = '2017-resultats-bruts/raw-resultats-district-'
cleanresultfile_base = '2017-resultats-bruts/clean-resultats-district-'

# each district has a "number" on votel website. This maps from votel number to district name.
mapping_district = {
    1:"Goms",
    2:"<NAME>",
    3:"Brig",
    4:"Visp",
    5:"<NAME>",
    6:"Leuk",
    7:"Sierre",
    8:"Hérens",
    9:"Sion",
    10:"Conthey",
    11:"Martigny",
    12:"Entremont",
    13:"St-Maurice",
    14:"Monthey",
}

district_seats = {
    "Goms":2,
    "<NAME>":1,
    "Brig":11,
    "Visp":11,
    "<NAME>":4,
    "Leuk":5,
    "Sierre":17,
    "Hérens":5,
    "Sion":18,
    "Conthey":11,
    "Martigny":17,
    "Entremont":6,
    "St-Maurice":5,
    "Monthey":17
}


############
# GET RAW RESULTS 2017 PER DISTRICT FROM VOTEL
############

# dl fichiers localement
for i in range(1,len(mapping_district)):
    url = urlbase+str(i)
    filename = rawresultfile_base+str(i)+".csv"
    print("Downloading "+url)
    r = requests.get(url)
    with open(filename,'w') as f:
        f.write(r.text)
        print("Written file "+filename)
############
# CLEAN RAW RESULTS PER DISTRICT AND GENERATE CLEAN FILES
############

def makeCleanResultsFile(district_num):
    rawf = rawresultfile_base+str(district_num)+'.csv'
    cleanf = cleanresultfile_base+str(district_num)+'.csv'
    with open(rawf) as f:
        lines = f.read().splitlines()
        # remove "empty" lines at top of file 
        # structure des fichiers -> commence à ligne 1 avec valeurs générales, 
        # puis 2 lignes blanches, puis scores par liste, 
        toplines_clean = lines[2:13]+lines[15:]
        # ensuite, après la prochaine ligne blanche commencent les scores individuels -> inutiles
        results_clean = toplines_clean[:toplines_clean.index('')]
    with open(cleanf,'w') as f:
        # append header "Commune"
        f.write('Commune'+'\n'.join(results_clean))
        print("Written file "+cleanf)
    
# for each file, generate clean results file
for i in range(1,len(mapping_district)):
    makeCleanResultsFile(i)
############
# AGGREGATE CLEAN RESULTS IN ONE FILE
############

# wrangle data (reindex + remove useless canton column + transpose)
def getCleanDf(district_num):
    cleanf = cleanresultfile_base+str(district_num)+'.csv'
    df = pd.read_csv(cleanf,delimiter=';')
    df = df.drop('Canton',axis=1) # remove canton score 
    df = df.set_index('Commune')
    df = df.transpose()
    df['District'] = mapping_district[district_num]
    return df

district_dfs = [getCleanDf(i) for i in range(1,len(mapping_district))] 
 
results = pd.concat(district_dfs)
results.head()<jupyter_output><empty_output><jupyter_text>Regroupement listes en groupes parlementaires<jupyter_code>results.columns
def findLists(allLists,pattern):
    return [ s for s in allLists if (pattern in s) ]
pdc = list(set(findLists(results.columns,'MOCRATE')))
pdc
cvp = findLists(results.columns,'CVP')
cvp
csp = findLists(results.columns,'CSP')
csp
verts = findLists(results.columns,'VERT')
verts
ps = list(set(findLists(results.columns,'ALLIANCE') + findLists(results.columns,'UNABHÄNGIGE')))
ps
udc = list(set(findLists(results.columns,'SVP') + findLists(results.columns,'UDC')))
udc
plr = list(set(findLists(results.columns,'LIB') + findLists(results.columns,'PLR')))
plr
rcv = findLists(results.columns,'RASSEMBLEMENT')
rcv
len(pdc+cvp+csp+verts+ps+udc+plr+rcv)
len(set(pdc+cvp+csp+verts+ps+udc+plr+rcv))
len(results.columns)
# map group name with actual party names on lists
map_group_parties = {
    "PDC":pdc,
    "CVP":cvp,
    "CSP":csp,
    "Verts":verts,
    "PS":ps,
    "UDC":udc,
    "PLR":plr,
    "RCV":rcv,
}
## Aggregate party votes - create a column "Total group"
all_results = results.fillna(0)
for group, parties in map_group_parties.items():
    all_results['Total '+group] = all_results[parties].sum(axis=1)

# total suffrages (tous les partis)
#all_parties = pdc+cvp+csp+verts+ps+udc+plr+rcv -> same but generic below
all_parties = [item for sublist in list(map_group_parties.values()) for item in sublist]
all_results['Somme Suffrages'] = all_results[all_parties].sum(axis=1)
all_results.head()<jupyter_output><empty_output><jupyter_text>Ajout nombre sièges par district, estimation électorat partisan<jupyter_code>## Extension - données calculées

# add number of seats per district
all_results['Sièges'] = all_results['District'].apply(lambda x: district_seats[x])

# calculer moyenne suffrages par district
all_results['Moyenne suffrages/bulletin'] = all_results['Somme Suffrages'] / all_results['Bulletins valables'] 

# estimer nombre d'électeurs par parti : base moyenne de suffrages par bulletin
for group, parties in map_group_parties.items():
    # version générique de : all_results['Electorat PDC'] = all_results['Total PDC'] / all_results['Moyenne suffrages/bulletin']
    all_results['Electorat '+group] = all_results['Total '+group] / all_results['Moyenne suffrages/bulletin']<jupyter_output><empty_output><jupyter_text>Observation : le nombre moyen de suffrages par bulletin est en moyenne égal à 97% du nombre de sièges pour le district. Outlier : Trient avec 81%. Les communes haut-valaisannes sont plus proches du 100% (listes franches?)**VIGILANCE** : ici je choisi d'estimer l'électorat sur la base de la moyenne de suffrages par bulletin. Mais dans les étapes suivantes, je vais multiplier ce chiffre par le nombre de sièges pour la région -> potentielle surestimation ! (mais en principe comparable pour tous les groupes donc négligeable ?)  Exportation<jupyter_code>with pd.ExcelWriter('2017-resultats-communes.xlsx') as writer:
    all_results.to_excel(writer)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13725490196078433, 'fraction_numerical': 0.014786242365798778, 'mean_word_length': 4.788837209302326, 'pattern_counts': {'":': 22, '<': 21, '<?xml version=': 0, '>': 26, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '325177', 'license': 'MIT', 'n_tokens_mistral': 2297, 'n_tokens_neox': 2204, 'n_words': 506, 'path': 'get-results.ipynb', 'repo_name': 'evequozf/Wallimandering', 'size': '6252'}