Update for 2024

This commit is contained in:
Aarni Koskela
2024-10-16 16:18:48 +03:00
parent de20fd9283
commit 1a0ae2502b
6 changed files with 69 additions and 51 deletions

View File

@@ -1,8 +1,8 @@
YEAR := 2023 YEAR := 2024
DATA_DIR := data/${YEAR} DATA_DIR := data/${YEAR}
OUT_DIR := out/${YEAR} OUT_DIR := out/${YEAR}
DOCUMENT_ID_FI := 1sycmd6DGqHj9-0k6D8HclzlRghxqoVaBZNSZye1Jdbg DOCUMENT_ID_FI := 1dvyVEJkn3_osBeKGIlhKmid671jjH7zYgcyH1BjiGF8
DOCUMENT_ID_EN := 1pmrQWsja3wRVF02PyEGO2F_CgttobTbxGUGjQ5K4H4Y DOCUMENT_ID_EN := 1o1uakk1pkoUCtx2OGJhLclxt_uraYA-uK3DH8yCYHN4
XLSX_URL_FI := https://docs.google.com/spreadsheets/d/$(DOCUMENT_ID_FI)/export?format=xlsx XLSX_URL_FI := https://docs.google.com/spreadsheets/d/$(DOCUMENT_ID_FI)/export?format=xlsx
TSV_URL_FI := https://docs.google.com/spreadsheets/d/$(DOCUMENT_ID_FI)/export?format=tsv TSV_URL_FI := https://docs.google.com/spreadsheets/d/$(DOCUMENT_ID_FI)/export?format=tsv
XLSX_URL_EN := https://docs.google.com/spreadsheets/d/$(DOCUMENT_ID_EN)/export?format=xlsx XLSX_URL_EN := https://docs.google.com/spreadsheets/d/$(DOCUMENT_ID_EN)/export?format=xlsx

View File

@@ -14,6 +14,7 @@
<li><a href="2021/">2021</a></li> <li><a href="2021/">2021</a></li>
<li><a href="2022/">2022</a></li> <li><a href="2022/">2022</a></li>
<li><a href="2023/">2023</a></li> <li><a href="2023/">2023</a></li>
<li><a href="2024/">2024</a></li>
</ul> </ul>
</body> </body>
</html> </html>

View File

@@ -19,7 +19,7 @@ PALVELUT_COL = "Palvelut"
ROOLI_COL = "Rooli" ROOLI_COL = "Rooli"
ROOLI_NORM_COL = "Rooli (normalisoitu)" ROOLI_NORM_COL = "Rooli (normalisoitu)"
SIIRTYNYT_COL = ( SIIRTYNYT_COL = (
"Oletko siirtynyt palkansaajasta laskuttajaksi tai päinvastoin 1.10.2022 jälkeen?" "Oletko siirtynyt palkansaajasta laskuttajaksi tai päinvastoin 1.10.2023 jälkeen?"
) )
SUKUPUOLI_COL = "Sukupuoli" SUKUPUOLI_COL = "Sukupuoli"
TUNTILASKUTUS_ALV0_COL = "Tuntilaskutus (ALV 0%, euroina)" TUNTILASKUTUS_ALV0_COL = "Tuntilaskutus (ALV 0%, euroina)"
@@ -30,10 +30,10 @@ VUOSILASKUTUS_ALV0_COL = "Vuosilaskutus (ALV 0%, euroina)"
VUOSITULOT_COL = "Vuositulot" VUOSITULOT_COL = "Vuositulot"
ID_COL = "Vastaustunniste" ID_COL = "Vastaustunniste"
COLUMN_MAP_2023 = { COLUMN_MAP_2024 = {
"Timestamp": "Timestamp", "Timestamp": "Timestamp",
"Oletko palkansaaja vai laskuttaja?": PALKANSAAJA_VAI_LASKUTTAJA_COL, "Oletko palkansaaja vai laskuttaja?": PALKANSAAJA_VAI_LASKUTTAJA_COL,
"Oletko siirtynyt palkansaajasta laskuttajaksi tai päinvastoin 1.10.2022 jälkeen?": SIIRTYNYT_COL, "Oletko siirtynyt palkansaajasta laskuttajaksi tai päinvastoin 1.10.2023 jälkeen?": SIIRTYNYT_COL,
"Ikä": "Ikä", "Ikä": "Ikä",
"Sukupuoli": "Sukupuoli", "Sukupuoli": "Sukupuoli",
"Työkokemus alalta (vuosina)": TYOKOKEMUS_COL, "Työkokemus alalta (vuosina)": TYOKOKEMUS_COL,
@@ -59,10 +59,10 @@ COLUMN_MAP_2023 = {
"Palautetta kyselystä ja ideoita ensi vuoden kyselyyn": PALAUTE_COL, "Palautetta kyselystä ja ideoita ensi vuoden kyselyyn": PALAUTE_COL,
} }
COLUMN_MAP_2023_EN_TO_FI = { COLUMN_MAP_2024_EN_TO_FI = {
"Timestamp": "Timestamp", "Timestamp": "Timestamp",
"Employee or entrepreneur": "Oletko palkansaaja vai laskuttaja?", "Employee or entrepreneur": "Oletko palkansaaja vai laskuttaja?",
"Have you switched from employment to entrepreneurship or vice versa after 1.10.2022?": "Oletko siirtynyt palkansaajasta laskuttajaksi tai päinvastoin 1.10.2022 jälkeen?", "Have you switched from employment to entrepreneurship or vice versa after 1.10.2023?": "Oletko siirtynyt palkansaajasta laskuttajaksi tai päinvastoin 1.10.2023 jälkeen?",
"Age": "Ikä", "Age": "Ikä",
"Gender": "Sukupuoli", "Gender": "Sukupuoli",
"Relevant work experience from the industry (in years)": "Työkokemus alalta (vuosina)", "Relevant work experience from the industry (in years)": "Työkokemus alalta (vuosina)",
@@ -89,9 +89,9 @@ COLUMN_MAP_2023_EN_TO_FI = {
} }
# ensure all columns have translations # ensure all columns have translations
assert set(COLUMN_MAP_2023.keys()) == set(COLUMN_MAP_2023_EN_TO_FI.values()) assert set(COLUMN_MAP_2024.keys()) == set(COLUMN_MAP_2024_EN_TO_FI.values())
VALUE_MAP_2023_EN_TO_FI = { VALUE_MAP_2024_EN_TO_FI = {
PALKANSAAJA_VAI_LASKUTTAJA_COL: { PALKANSAAJA_VAI_LASKUTTAJA_COL: {
"Employee": "Palkansaaja", "Employee": "Palkansaaja",
"Entrepreneur": "Laskuttaja", "Entrepreneur": "Laskuttaja",
@@ -112,8 +112,16 @@ VALUE_MAP_2023_EN_TO_FI = {
"Finland": "Suomesta", "Finland": "Suomesta",
}, },
KAUPUNKI_COL: { KAUPUNKI_COL: {
"PK-Seutu (Helsinki, Espoo, Vantaa)": "PK-seutu", "Asun Porissa, toimisto Helsingissä, sijainnilla ei vaikutusta palkkaan": "Pori",
"Capital region (Helsinki, Espoo, Vantaa)": "PK-seutu", "Capital region (Helsinki, Espoo, Vantaa)": "PK-seutu",
"Firmalla ei ole toimistoa": "Etätyöfirma",
"Hajautettu": "Etätyöfirma",
"New York City": "New York",
"New York, NY, USA": "New York",
"PK-Seutu (Helsinki, Espoo, Vantaa)": "PK-seutu",
"Tampere (etänä Berliiniin)": "Tampere",
"Turku/remote (HQ Austin, TX)": "Turku",
"Ulkomailla": "Ulkomaat",
}, },
MILLAISESSA_COL: { MILLAISESSA_COL: {
"Product company with softaware as their core business": "Tuotetalossa, jonka core-bisnes on softa", "Product company with softaware as their core business": "Tuotetalossa, jonka core-bisnes on softa",
@@ -166,23 +174,44 @@ ROLE_MAP = {
"Full-stack cloud developer": FULL_STACK_ROLE, "Full-stack cloud developer": FULL_STACK_ROLE,
"Fullstack developer, web apps": FULL_STACK_ROLE, "Fullstack developer, web apps": FULL_STACK_ROLE,
} }
NO_GENDER_VALUES = { NO_GENDER_VALUES = {
"-", "-",
"ei liity asiaan", "on",
"epärelevantti",
"jänis",
"kyllä, kiitos",
"leppäkerttu",
"tihkutympönen",
"yes", "yes",
} }
OTHER_GENDER_VALUES = { OTHER_GENDER_VALUES = {
"muu", "muu",
"muu/ei", "muunsukupuolinen",
"non-binary, afab",
} }
FEMALE_GENDER_VALUES = (
"f",
"n",
"women",
)
MALE_GENDER_VALUES = (
"he / him / male",
"ihminen. kikkelillä.",
"m i ä s",
"m",
"mail", # probably a typo
"male presenting",
"male",
"man",
"meis",
"mie", # probably mies?
"miekkonen",
"mies",
"miesoletettu",
"miäs",
"ukko",
"äiä",
)
IDS_TO_DROP = { IDS_TO_DROP = {
"6cab61607da9c2b6", # hupsu taisteluhelikopteri "0bf579f8b0a771b9", # 2 euron palkka, rooli "2"
"aefdb9e69b1621d5", # See "SUBMITTED TWICE, SORRY!!" in English data "9a3b73d810f6e983", # apache hyökkäyshelikopteri
} }

View File

@@ -10,9 +10,10 @@ import pandas as pd
from pulkka.column_maps import ( from pulkka.column_maps import (
BOOLEAN_TEXT_TO_BOOLEAN_MAP, BOOLEAN_TEXT_TO_BOOLEAN_MAP,
COLUMN_MAP_2023, COLUMN_MAP_2024,
COLUMN_MAP_2023_EN_TO_FI, COLUMN_MAP_2024_EN_TO_FI,
COMPANY_MAP, COMPANY_MAP,
FEMALE_GENDER_VALUES,
ID_COL, ID_COL,
IDS_TO_DROP, IDS_TO_DROP,
IKA_COL, IKA_COL,
@@ -23,6 +24,7 @@ from pulkka.column_maps import (
KKPALKKA_COL, KKPALKKA_COL,
LAHITYO_COL, LAHITYO_COL,
LANG_COL, LANG_COL,
MALE_GENDER_VALUES,
NO_GENDER_VALUES, NO_GENDER_VALUES,
OTHER_GENDER_VALUES, OTHER_GENDER_VALUES,
PALVELUT_COL, PALVELUT_COL,
@@ -33,7 +35,7 @@ from pulkka.column_maps import (
TYOAIKA_COL, TYOAIKA_COL,
TYOKOKEMUS_COL, TYOKOKEMUS_COL,
TYOPAIKKA_COL, TYOPAIKKA_COL,
VALUE_MAP_2023_EN_TO_FI, VALUE_MAP_2024_EN_TO_FI,
VUOSITULOT_COL, VUOSITULOT_COL,
) )
from pulkka.config import DATA_DIR, YEAR from pulkka.config import DATA_DIR, YEAR
@@ -49,19 +51,11 @@ def map_sukupuoli(r: pd.Series) -> str | None:
"nainen" in value "nainen" in value
or "female" in value or "female" in value
or "woman" in value or "woman" in value
or value == "f" or value in FEMALE_GENDER_VALUES
or value == "women"
): ):
return "nainen" return "nainen"
if ( if value.strip() in MALE_GENDER_VALUES:
"mies" in value
or "uros" in value
or "miäs" in value
or "äiä" in value
or "male" in value
or value in ("m", "man", "m i ä s", "ukko")
):
return "mies" return "mies"
if value in NO_GENDER_VALUES: if value in NO_GENDER_VALUES:
@@ -70,7 +64,7 @@ def map_sukupuoli(r: pd.Series) -> str | None:
if value in OTHER_GENDER_VALUES: if value in OTHER_GENDER_VALUES:
return "muu" return "muu"
raise NotImplementedError(f"Unknown sukupuoli: {value} (row ID {r[ID_COL]})") raise NotImplementedError(f"Unknown sukupuoli: {value!r} (row ID {r[ID_COL]})")
def map_vuositulot(r): def map_vuositulot(r):
@@ -110,7 +104,7 @@ def read_initial_dfs() -> pd.DataFrame:
skiprows=[1], # Google Sheets exports one empty row skiprows=[1], # Google Sheets exports one empty row
) )
df_en[LANG_COL] = "en" df_en[LANG_COL] = "en"
df_en = df_en.rename(columns=COLUMN_MAP_2023_EN_TO_FI) df_en = df_en.rename(columns=COLUMN_MAP_2024_EN_TO_FI)
df = pd.concat([df_fi, df_en], ignore_index=True) df = pd.concat([df_fi, df_en], ignore_index=True)
df = df[df["Timestamp"].notna()] # Remove rows with no timestamp df = df[df["Timestamp"].notna()] # Remove rows with no timestamp
df[LANG_COL] = df[LANG_COL].astype("category") df[LANG_COL] = df[LANG_COL].astype("category")
@@ -130,23 +124,24 @@ def map_case_insensitive(series: pd.Series, mapping: dict[str, str]) -> pd.Serie
def map_value(v): def map_value(v):
if v is np.nan: if v is np.nan:
return "" return ""
assert isinstance(v, str) if not isinstance(v, str):
raise TypeError(f"Unexpected value {v!r} of type {type(v)}")
return lower_mapping.get(v.lower().strip(), v) return lower_mapping.get(v.lower().strip(), v)
return series.apply(map_value).fillna(series) return series.apply(map_value).fillna(series)
def read_data() -> pd.DataFrame: def read_data() -> pd.DataFrame:
if YEAR != "2023": if YEAR != "2024":
raise ValueError( raise ValueError(
"This code only works for 2023. " "This code only works for 2024. "
"Please use an older revision for older data.", "Please use an older revision for older data.",
) )
df = read_initial_dfs() df = read_initial_dfs()
df = df.rename(columns=COLUMN_MAP_2023) df = df.rename(columns=COLUMN_MAP_2024)
for col, val_map in VALUE_MAP_2023_EN_TO_FI.items(): for col, val_map in VALUE_MAP_2024_EN_TO_FI.items():
df[col] = df[col].map(val_map).fillna(df[col]).astype("category") df[col] = df[col].map(val_map).fillna(df[col]).astype("category")
# Drop known bogus data # Drop known bogus data
@@ -192,14 +187,7 @@ def read_data() -> pd.DataFrame:
df = apply_fixups( df = apply_fixups(
df, df,
[ [
( # ({ID_COL: "..."}, {VUOSITULOT_COL: 62000}),
{ID_COL: "a01216a11026d749", VUOSITULOT_COL: 620000},
{VUOSITULOT_COL: 62000},
),
(
{ID_COL: "79a200f529f6919b", VUOSITULOT_COL: 1500},
{VUOSITULOT_COL: 150_000},
),
], ],
) )
# Fill in Vuositulot as 12.5 * Kk-tulot if empty # Fill in Vuositulot as 12.5 * Kk-tulot if empty

View File

@@ -33,7 +33,7 @@ def get_categorical_stats(
if na_as_category: if na_as_category:
rename_na(df, category_col, na_as_category) rename_na(df, category_col, na_as_category)
# ... then carry on. # ... then carry on.
group = df[[category_col, value_col]].groupby(category_col) group = df[[category_col, value_col]].groupby(category_col, observed=False)
return group[value_col].agg( return group[value_col].agg(
["mean", "min", "max", "median", "count", q25, q50, q75, q90], ["mean", "min", "max", "median", "count", q25, q50, q75, q90],
) )

View File

@@ -79,7 +79,7 @@ def main():
) )
env.globals.update( env.globals.update(
{ {
"date": datetime.datetime.utcnow(), "date": datetime.datetime.now(datetime.UTC),
"cm": column_maps, "cm": column_maps,
"pd": pandas, "pd": pandas,
"np": numpy, "np": numpy,