Cookies op Tweakers

Tweakers maakt gebruik van cookies, onder andere om de website te analyseren, het gebruiksgemak te vergroten en advertenties te tonen. Door gebruik te maken van deze website, of door op 'Ga verder' te klikken, geef je toestemming voor het gebruik van cookies. Wil je meer informatie over cookies en hoe ze worden gebruikt, bekijk dan ons cookiebeleid.

Meer informatie
Toon posts:

Python, pandas uurtotaal verdelen

Pagina: 1
Acties:

Vraag


Acties:
  • +1Henk 'm!

  • Fonkymunkey
  • Registratie: april 2007
  • Laatst online: 25-10 18:33
Hoi,

===============================
EDIT:
Ik heb het probleem gevonden maar ben nog wel benieuwd of er een andere, efficiëntere manier is om dit projectje aan te pakken in pandas. Mocht iemand een suggestie hebben, graag :)
===============================


Ik ben kort geleden begonnen met python en om te oefenen heb ik een klein project bedacht. Nee geen schoolopdracht maar echt uitdaging voor mezelf.

Wat gebruik ik:
Python 3.7, Pycharm, Jupyter notebook en de Pandas library

Wat heb ik:
- Weerdata voor elk uur voor 2011 en 1212
- Fietsdiefstallen over 2011-2012
- Berovingen in 2011-2012


Wat wil ik:
- Alle data combineren in één tabel/dataframe
- Verbanden zichtbaar maken


Ik ben nu bezig met het samenbrengen van de data en het is me bíjna gelukt. Maar ik maak ergens een denkfout, gebruik de verkeerde manier, zie iets over het hoofd... Het totaal aantal diefstallen wijkt uiteindelijk ongeveer 2000 af. Ik vraag me af wat er nu misgaat en hoe ik de uurtotalen misschien anders kan berekenen. Ik hoop dat hier iemand iets kan verhelderen.

Best een lange post geworden dus bedankt voor het lezen alvast :)

Mijn code en uitleg staan onderaan.

Ik heb de volgende dataframes:

Weerdata


Fietsdiefstallen:


Berovingen:


Hoe werkt mijn code:

- Ik heb een dataframe df_complete met één rij voor élk uur in 2011-2012 :
(2*365 + 1schrikkeldag) * 24 uur = 17544 uur

- Ik verdeel de fietsdiefstallen over de uren waarin ze plaatsvinden
Dus stel de diefstal is tussen 00:00 en 05:00, dat zal deze diefstal 1/5 bijdragen aan het uurtotaal van de uren waarin hij plaatsvindt. Ter verduidelijking:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    T1: theft 1, start= h0, end = h4, duration = 5 hours
    T2: theft 2, start= h1, end = h3, duration = 3 hours
    T3: theft 3, start= h4, end = h7, duration = 4 hours
    T4: theft 4, start= h7, end = h9, duration = 3 hours

         T1    T2/T4   T3     summed weights
    h0   .2                  .2
    h1   .2   .33            .53
    h2   .2   .33            .53
    h3   .2   .33            .53
    h4   .2           .25    .45
    h5                .25    .25
    h6               . 25    .25
    h7        .33     .25    .58
    h8        .33            .33
    h9        .33            .33


- Op basis van de datetime van de berovingen wordt één opgeteld bij het totaal voor het uur waarin hij plaatsvindt.


De code voor het combineren van de data:
df_complete : dataframe met alle data
df_bt : Bike Thefts
df_rb : RoBberries

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# COMBINE DATA ===========================================

# set the datetime column as index
df_complete.set_index('datetime', inplace=True)

# THEFTS
# get start, end and weight for each theft and add the weight
# to corresponding range of rows in df_complete
for i in range(len(df_bt)):
    
    start = df_bt.loc[i, 'bt_start']
    # substract a timedelta of one hour from 'end' because in
    # df_complete.loc[start:end, 'thefts'] , 'end' is inclusive
    end = df_bt.loc[i, 'bt_end'] - pd.Timedelta(1, 'h')
    weight = df_bt.loc[i, 'weight']
    df_complete.loc[start:end, 'thefts'] += weight

# ROBBERIES
# get the datetime of each robbery in df_rb and add 1
# to the corresponding row in df_complete
for i in range(len(df_rb)):
    
    robb = df_rb.iloc[i, 0]
    df_complete.loc[robb, 'robberies'] += 1


df_complete.to_csv(r'results\complete.csv')

#df_complete.head(50)

Total = df_complete['thefts'].sum()
print (Total)


Ik weet dat het eigenlijk een no-no is om te loopen over een dataframe. Ik ken op dit moment de andere(?) opties niet.


Het resultaat:


Het verdelen van de overvallen gaat helemaal goed.

Wat het niet helemaal goed gaat:
- er zijn diefstallen met een einddatum in 2013 (ongeveer 30) maar ik krijg geen out of range error bij het toevoegen
- De som van alle waarden uit de kolom 'thefts' komt op 14309 terwijl het aantal diefstallen 16177 is. Doordat er een klein deel in 2013 eindigt zal de som wat lager uitvallen maar 2000 lijkt me veel te veel.

Ik gok dat er iets niet klopt in dit stukje code:
code:
1
2
3
4
5
6
7
8
for i in range(len(df_bt)):
    
    start = df_bt.loc[i, 'bt_start']
    # substract a timedelta of one hour from 'end' because in
    # df_complete.loc[start:end, 'thefts'] , 'end' is inclusive
    end = df_bt.loc[i, 'bt_end'] - pd.Timedelta(1, 'h')
    weight = df_bt.loc[i, 'weight']
    df_complete.loc[start:end, 'thefts'] += weight

[Voor 8% gewijzigd door Fonkymunkey op 25-05-2020 17:34. Reden: info toegevoegd]


Acties:
  • 0Henk 'm!

  • Fonkymunkey
  • Registratie: april 2007
  • Laatst online: 25-10 18:33
Okeeh... het probleem:

Is nu opgelost :)


Maar....netjes en efficient ...? Ehm nee.

Weet iemand een betere aanpak ? Zijn en functies in pandas die niet ken maar wel kan gebruiken ?

Thanks

code:
1
2
3
4
5
6
7
8
for i in range(len(df_bt)):
    
    start = df_bt.loc[i, 'bt_start']
    # substract a timedelta of one hour from 'end' because in
    # df_complete.loc[start:end, 'thefts'] , 'end' is inclusive
    end = df_bt.loc[i, 'bt_end'] - pd.Timedelta(1, 'h')
    weight = df_bt.loc[i, 'weight']
    df_complete.loc[start:end, 'thefts'] += weight


- Omdat bij
code:
1
df_complete.loc[start:end, 'thefts'] += weight

het einde van de range (end) inclusive is heb ik één uur afgehaald van de eindtijd (- pd.Timedelta(1, 'h'))
Als ik dit niet deed werd het totaal veel te hoog.

Maar ja als start en end gelijk zijn, komt end vóór start te liggen, een ongeldige range en niet toegevoegd.

Ik heb de volgende workaround die werkt, mijn totaal komt nu op 16160 en dat ligt maar 18 onder het totaal van 16178 diefstallen.

code:
1
2
3
4
5
6
7
8
9
10
11
12
for i in range(len(df_bt)):
    
    start = df_bt.loc[i, 'bt_start']
    # substract a timedelta of one hour from 'end' because in
    # df_complete.loc[start:end, 'thefts'] , 'end' is inclusive
    end = df_bt.loc[i, 'bt_end']# - pd.Timedelta(1, 'h')

    if start != end:                                       # <<< toegevoegd
        end -= pd.Timedelta(1, 'h')
    
    weight = df_bt.loc[i, 'weight']
    df_complete.loc[start:end, 'thefts'] += weight

Beste antwoord (via Fonkymunkey op 13-06-2020 19:24)


  • Morrar
  • Registratie: juni 2002
  • Laatst online: 00:33
Voor de berovingen kun je gewoon een aggregatie doen op de datetime toch en daarna joinen:

Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

# Wat voorbeeld data in hetzelfde formaat
df_rb = pd.DataFrame({
  "datetime": ["2020-01-01 18:00", "2020-01-01 18:00", "2020-01-01 19:00"]
}).set_index("datetime")

# Vul 1 beroving in op elke rij
df_rb["robberies"] = 1

# Bereken het totaal per datetime (er van uitgaande dat dit de index is)
df_rb = df_rb.groupby(level=0).agg({"robberies": sum})

# En dan mergen met de rest... (join gebruikt de datetime index)
df_complete = df_complete.join(df_rb)

# Optioneel: vul nul in voor de ontbrekende waardes
df_complete = df_complete.fillna({"robberies": 0})


Voorkomt alvast een for loop.

De fietsendiefstallen zijn wat lastiger, omdat je dan eigenlijk een "between" join wilt doen die pandas niet kent (itt SQL)... Wat je zou kunnen doen is eerst de records maken per diefstal en deze dan optellen:

Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd

# Voorbeeld data
df = pd.DataFrame({
  "dt_start": ["2020-01-01 18:00", "2020-01-01 19:00"],
  "duration": [2, 3],
  "weight": [0.5, 0.33],
})

# Functie die dataframe per diefstal maakt
def explode(row):
  return pd.DataFrame({
    "datetime": pd.date_range(start=row["dt_start"], periods=row["duration"], freq="H"),
    "theft": row["weight"] / row["duration"]
  })

# Explode alle rijen en voeg ze weer samen tot 1 data frame
df = pd.concat([
  explode(row) for _, row in df.iterrows()
])

# Aggregeer per uur zodat je kunt joinen
df = df.groupby("datetime").agg({"theft": sum})


Zit nog steeds een for loop in, maar wellicht is het wat leesbaarder. Valt eventueel ook vrij makkelijk te paralleliseren zo trouwens.


P.S. Mijn voorbeeldjes gebruiken strings ipv datetime maar idee is hetzelfde.

[Voor 63% gewijzigd door Morrar op 25-05-2020 19:15]

Alle reacties


Acties:
  • Beste antwoord
  • +2Henk 'm!

  • Morrar
  • Registratie: juni 2002
  • Laatst online: 00:33
Voor de berovingen kun je gewoon een aggregatie doen op de datetime toch en daarna joinen:

Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

# Wat voorbeeld data in hetzelfde formaat
df_rb = pd.DataFrame({
  "datetime": ["2020-01-01 18:00", "2020-01-01 18:00", "2020-01-01 19:00"]
}).set_index("datetime")

# Vul 1 beroving in op elke rij
df_rb["robberies"] = 1

# Bereken het totaal per datetime (er van uitgaande dat dit de index is)
df_rb = df_rb.groupby(level=0).agg({"robberies": sum})

# En dan mergen met de rest... (join gebruikt de datetime index)
df_complete = df_complete.join(df_rb)

# Optioneel: vul nul in voor de ontbrekende waardes
df_complete = df_complete.fillna({"robberies": 0})


Voorkomt alvast een for loop.

De fietsendiefstallen zijn wat lastiger, omdat je dan eigenlijk een "between" join wilt doen die pandas niet kent (itt SQL)... Wat je zou kunnen doen is eerst de records maken per diefstal en deze dan optellen:

Python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd

# Voorbeeld data
df = pd.DataFrame({
  "dt_start": ["2020-01-01 18:00", "2020-01-01 19:00"],
  "duration": [2, 3],
  "weight": [0.5, 0.33],
})

# Functie die dataframe per diefstal maakt
def explode(row):
  return pd.DataFrame({
    "datetime": pd.date_range(start=row["dt_start"], periods=row["duration"], freq="H"),
    "theft": row["weight"] / row["duration"]
  })

# Explode alle rijen en voeg ze weer samen tot 1 data frame
df = pd.concat([
  explode(row) for _, row in df.iterrows()
])

# Aggregeer per uur zodat je kunt joinen
df = df.groupby("datetime").agg({"theft": sum})


Zit nog steeds een for loop in, maar wellicht is het wat leesbaarder. Valt eventueel ook vrij makkelijk te paralleliseren zo trouwens.


P.S. Mijn voorbeeldjes gebruiken strings ipv datetime maar idee is hetzelfde.

[Voor 63% gewijzigd door Morrar op 25-05-2020 19:15]


Acties:
  • +4Henk 'm!

  • Fonkymunkey
  • Registratie: april 2007
  • Laatst online: 25-10 18:33
Dank voor je input Morrar :)

Ik zal eerst even kijken naar groupby en aggregate. Ik zou natuurlijk gewoon kunnen copy-pasten en dan werk het vast, maar ik ga ook proberen uit te zoeken hoe het werkt en waarom. Ik wil er van leren tenslotte.

Soms roepen antwoorden meer vragen op dan je van te voren had :)

En paralleliseren, term ben ik wel tegengekomen maar nog een beetje 'too advanced' op dit moment denk ik.

thanks, Ik heb weer wat te doen

Ik ga ermee aan de slag

Acties:
  • +2Henk 'm!

  • Morrar
  • Registratie: juni 2002
  • Laatst online: 00:33
Heel goed om het zelf uit te zoeken!

Kort gezegd zorgt groupby() voor een groepering van records op een bepaalde kolom of index. Records die dezelfde waarde hebben op die kolom/index worden samengenomen tot 1 groep.

Met agg() kun je aangeven hoe die groepen records "samengevat" moeten worden. Je kunt een dict meegeven met kolom:functienaam, bijvoorbeeld:

Python:
1
2
3
4
{
    "prijs": "mean", 
    "aantal": "sum", 
}


Zou het gemiddelde uitrekenen voor de kolom prijs en het totaal van de kolom aantal.

[Voor 3% gewijzigd door Morrar op 25-05-2020 20:24]


Acties:
  • +1Henk 'm!

  • Fonkymunkey
  • Registratie: april 2007
  • Laatst online: 25-10 18:33
Bedankt voor de input !
Bij deze nog even de resultaten:

De correlatie heatmap


Verband tussen temperatuur en fietsdiestal (temp = blauw, diefstal = oranje)

Acties:
  • +1Henk 'm!

  • Morrar
  • Registratie: juni 2002
  • Laatst online: 00:33
Mooi dat het gelukt is!

Qua resultaten; beetje lastig om de correlatie tussen diefstal en temperatuur te zien. Temperatuur laat natuurlijk flinke seizoenaliteit zien en de vraag is of dat handig is in dit geval.

In diefstallen zie je veel minder een seizoenspatroon terug; waarschijnlijk wel sterke wekelijkse seizoenaliteit met in de weekenden fors meer diefstallen? Maar dat is lastig te zien in het plaatje op deze schaal... In de winter wordt er wel wat minder gestolen lijkt het; vermoed dat het komt omdat er dan minder fietsen "semi onbewaakt" buiten staan (met de eigenaar op het terras) of omdat er minder gelegenheids diefstallen zijn (fietsje jatten om na het uitgaan thuis te komen). In 2012 lijkt er een beetje een zomervakantie dip te zijn, maar dat is speculatie...

Wat waren eigenlijk je hypotheses bij deze data set?

  • Fonkymunkey
  • Registratie: april 2007
  • Laatst online: 25-10 18:33
Ik had niet echt hypotheses, het is/was een projectje om pandas te verkennen en te zien wat er uit zou komen. Mijn doel was om te kijken of er misschien een verband kon worden gelegd tussen een weersomstandigheid en diefstallen/overvallen.

Ik heb mooi weer (zon en temperatuur) en fietsdiefstal uitgelicht omdat de heatmap een hogere correlatie liet zien.

Tja en dan de interpretatie, dat is een ander verhaal:
- Gaan fietsendieven er meer op uit als het mooi weer is ?
- Is er meer gelegenheid omdat mensen met mooi weer meer fietsen (naar hun werk) ?
- Is er een dip in diefstallen in de vakantieperiode omdat mensen mider fietsen (naar hun werk) ?
- Neemt het aantal diefstallen af in periodes dat het langer licht is per dag ?

Voor zulke vragen zou je moeten inzoomen en bv meer data nodig hebben als daglicht per uur, aantal diefstallen in/na uitgaansuren enz.

Maar voor nu vind ik het een leuke eerste kennismaking met pandas en ben ik intussen alweer bezig met een ander projectje. Python code voor het uit mn hoofd leren van alle cijfers bij de letters, en letters bij de cijfers uit het alfabet :)

2 5 4 1 14 11 20 - 22 15 15 18 - 10 5 - 9 14 16 21 20 !


code:
1
2
3
if trainingmode:
    print('A B C D E F G H I J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z' )
    print('1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26')

[Voor 11% gewijzigd door Fonkymunkey op 23-06-2020 21:28]

Pagina: 1


Apple iPhone 12 Microsoft Xbox Series X LG CX Google Pixel 5 Black Friday 2020 Samsung Galaxy S20 4G Sony PlayStation 5 Nintendo Switch Lite

Tweakers vormt samen met Hardware Info, AutoTrack, Gaspedaal.nl, Nationale Vacaturebank, Intermediair en Independer DPG Online Services B.V.
Alle rechten voorbehouden © 1998 - 2020 Hosting door True