Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Python: Tk_tools: Gini-kerroin

Sivun loppuun

koodaaja [16.06.2021 00:11:03]

#

Tämä ohjelma laskee Gini-kertoimen käyttäjän antamista tuloista. Gini-kerroin kuvaa tuloeroja ja se on 0-1 välillä. Gini-kerroin lasketaan kaavasta A/(A+B) tai A/0,5. Tässä lasketaan A:n pinta-ala ja lasketaan kaavasta A/0,5 Gini-kerroin. Gini-kerroin voidaan esittää graafisesti, jossa on kaksi käyrää toinen käyrä kuvaa, kun tulot menevät tasan ja toinen käyrä kuvaa todellista tilannetta A on näiden välissä. B on A:n alapuolella. Näin ollen A+B on 0,5.

from tkinter import *
from tkinter.ttk import *
import tkinter as tk
import tk_tools
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
import matplotlib.pyplot as plt
import numpy as np
from itertools import accumulate
from operator import add

root = Tk()



def laskenta(tulot):
 tulot.insert(0,0)
 i = len(tulot)-1

 #Lasketaan gini-kerroin

 if (len(tulot) > 0):
  tulot.sort()

 summa = 0.0
 summa = sum(tulot)

 #Lasketaan osuudet ja kumulatiiviset osuudet lambdalla ja accumulatella

 osuus = list(map(lambda x: x/summa, tulot))

 kumulatiivinen = list(accumulate(osuus,add))

 #Lasketaan pinta_ala

 pinta_ala = []

 pinta_ala = list(map(lambda x: (1.0/i)*((kumulatiivinen[x-1]+kumulatiivinen[x])/2),   range(1,len(kumulatiivinen))))

 pa_summa = sum(pinta_ala)

 gini = (0.5-pa_summa)/0.5
 return round(gini*10000.0)/10000,kumulatiivinen

def piirraKayrat(tulot,kumulatiivinen):
 uusi = Toplevel(root)
 uusi.title("New Window")
 uusi.geometry("500x500")
 fig = Figure(figsize=(5,5),dpi=100)

 #Lasketaan luvut käyrää varten.
 i = len(tulot)-1
 y = [a/i for a in range(0,i+1)]

 y2 = kumulatiivinen
 #Piirretään käyrät.
 plot1 = fig.add_subplot(111)

 plot1.plot(y)

 plot1.plot(y2)
 canvas = FigureCanvasTkAgg(fig,master=uusi)
 canvas.get_tk_widget().grid()

def poista(event):
 #Tuplaklikkaaminen poistaa tiedon.
 valinta = lst.curselection()
 lst.delete(valinta)
 lst2.delete(valinta)



matala = 0
keskitaso = 0
korkea = 0
def pylvas(tulot,kumulatiivinen):

 i = len(tulot)-1
 N = len(kumulatiivinen)
 ind = np.arange(N)
 width = 0.35
 fig = plt.figure()

 ax = fig.add_axes([0,0,1,1])
 ax.bar(ind,kumulatiivinen,width,color='b')
 ax.set_ylabel("Kumulatiivinen")
 ax.legend("tulot")

 plt.show()

def Kayttoliittyma():
 global korkea,keskitaso,matala
 #Syötetään tulo sekä niiden määrä ja lisätään se summaan.

 syote = e1.get().split(',')
 try:
  tulot = [float(s) for s in syote]
  valinta = int(CheckVar.get())

  gini,kumulatiivinen = laskenta(tulot)
  if (gini > 0.4):
   label.config(text=str(gini),bg="red")
   korkea += 1
  elif (gini > 0.3):
   label.config(text=str(gini),bg="yellow")
   keskitaso += 1
  else:
   label.config(text=str(gini),bg="green")
   matala += 1

  label2.config(text=str(korkea))
  label3.config(text=str(keskitaso))
  label4.config(text=str(matala))

  if (valinta == 1):
   #Piirretään
   piirraKayrat(tulot,kumulatiivinen)
   pylvas(tulot,kumulatiivinen)
  lst.insert(END, "Gini-kerroin:" + str(gini))
  lst2.insert(END, str(tulot))
 except:
  e1.config(bg="red")


frame = Frame(root)
frame.pack()
frame2 = Frame(root)
frame2.pack()

frame3 = Frame(root)
frame3.pack()

frame4 = Frame(root)
frame4.pack()

var = StringVar()
CheckVar = IntVar()

#Luodaan kontrollit.
l1 = Label(frame,textvariable=var)
l1.grid(row=1,column=1)
e1 = tk.Entry(frame,bg="white")
e1.grid(row=1,column=2)

var.set("Tulo:")

#Luodaan listaan skrollaava toiminto
scroll = Scrollbar(frame3)
scroll.pack(side=RIGHT,fill=Y)
lst = Listbox(frame3, yscrollcommand=scroll.set)
lst.pack(side=LEFT,fill=BOTH)
scroll.config(command=lst.yview)
lst.bind('<Double-1>',poista)

scroll2 = Scrollbar(frame4)
scroll2.pack(side=RIGHT,fill=Y)
lst2 = Listbox(frame4,yscrollcommand=scroll2.set)
lst2.pack(side=LEFT,fill=BOTH)
scroll2.config(command=lst2.yview)

btn = Button(frame2,text="Laske",command=Kayttoliittyma)
btn.grid()

check1 = Checkbutton(frame2,text="Piirrä",variable=CheckVar,onvalue=1,offvalue=0)
check1.grid()

label = tk.Label(frame2,text="teksti",bg="white")
label.grid()

label2 = tk.Label(frame2,text="",bg="red")
label2.grid()

label3 = tk.Label(frame2,text="",bg="yellow")
label3.grid()

label4 = tk.Label(frame2,text="",bg="green")
label4.grid()

root.mainloop()

Metabolix [16.06.2021 00:45:59]

#

koodari kirjoitti:

Gini-kerroin lasketaan kaavasta A/(A+B) tai A/0,5.

Olisi hirmu hienoa, jos kertoisit myös, kenen tuloeroja se Gini-kerroin kuvaa ja millä tavalla sitä tulkitaan ja mitä ovat kaavassa A ja B (eli mistä ne tulevat). Koodissa olisi järkevää erottaa jotenkin tuo laskenta ja käyttöliittymä, ja koko käyttöliittymän voisi tehdä samalla tavalla (eli joko tekstinä tai Tk-käyttöliittymänä, ei sekaisin).

Grez [16.06.2021 07:17:39]

#

Selvästikin se laskee annetun tulojoukon Gini-kertoimen. Eli jos ohjelmalle syötettäisiin jokaisen suomalaisen tulot, niin tuloksena olisi suomalaisten Gini-kerroin.

Lisää aiheesta
https://fi.wikipedia.org/wiki/Gini-kerroin

koodaaja [16.06.2021 14:19:43]

#

Paransi koodia. Nyt se on kokonaan toteutettu Tk-käyttöliittymänä ja lisäksi se piirtää Lorenz-käyrän.

jalski [16.06.2021 18:39:14]

#

koodaaja kirjoitti:

Paransi koodia. Nyt se on kokonaan toteutettu Tk-käyttöliittymänä ja lisäksi se piirtää Lorenz-käyrän.

Jos tarkoitus on demota käyttöliittymän tekoa TK:lla, niin kannattaa toteuttaa se järjellisesti. Nyt käyttöliittymässä ei ole selitteitä missään. Mitä syötetään ja mitä ovat nuo ruudukkoon tulevat summat? Lisäksi esimerkiksi kuvaajan piirto on nimellä "Uusi ikkuna" ja jos arvoja ei ole syötetty niin tuon toiminnon seurauksena on "ZeroDivisionError".

koodaaja [17.06.2021 18:24:56]

#

Ohjelmaa on hieman parannettu. Lisäksi taulukko on korvattu poistettavilla tekstikentillä.

jalski [17.06.2021 20:52:38]

#

koodaaja kirjoitti:

Ohjelmaa on hieman parannettu. Lisäksi taulukko on korvattu poistettavilla tekstikentillä.

Vieläkin voisit tehdä skaalautuvan käyttöliittymän ja järkeistää sijoittelun. Eikö noita poistettavia tekstikenttiä saa jonkun freimin sisälle mikä olisi varustettu vierityspalkilla. Sillä tavalla ohjelmaikkunan koko ei kasvaisi automaattisesti kun arvoja syötetään. Lisäksi itse ohjelmaan pitäisi lisätä syötteen tarkistus siten ettei ohjelmaa voisi saada virheelliseen tilaan.

Oma keskeneräinen tuotokseni, johon lisäsin syötekentän validoinnin ja rajojen korostamisen punaisella kun syöte on virheellinen.

koodaaja [17.06.2021 21:53:46]

#

Korvasin poistettavat tekstikentät listalla, josta voi tuplaklikkaamalla ottaa tiedon pois. Lisäksi listassa on skrollaustoiminto.

jalski [18.06.2021 00:08:55]

#

koodaaja kirjoitti:

Korvasin poistettavat tekstikentät listalla, josta voi tuplaklikkaamalla ottaa tiedon pois. Lisäksi listassa on skrollaustoiminto.

Täällä on minun versioni 8th:lla. Kuvaajanpiirtoa en toteuttanut ja enterin painallus olisi varmaan hyvä linkittää lisäys nappiin. Käyttöliittymä on Nuklear pohjainen, eli käyttöliittymän tila pitää tallentaa itse ja esimerkiksi listan toiminnalisuus pitää toteuttaa itse. Omaan listaani toteutin lisäyksen, valinnan ja poiston.

koodaaja [18.06.2021 20:31:56]

#

Syöttöä on muokattu siten, että kaikki tulot kerrotaan kerralla ja tulot on erotettu pilkuilla.

jalski [18.06.2021 22:38:05]

#

koodaaja kirjoitti:

Syöttöä on muokattu siten, että kaikki tulot kerrotaan kerralla ja tulot on erotettu pilkuilla.

Edelleenkään et tarkista syötettä ja lopputulos on huono, jos käyttäjä syöttää vahingossa vääränlaisen syötteen. Lisäksi käyttöliittymä vaatisi hienosäätöä. Esimerkiksi oletuksena se ei mahdu kokonaan ikkunaan.

Toteutin omaan versiooni syötekentän validoinnin tilakoneen avulla. Paransin myös käyttöliittymän toimivuutta. Video toiminnasta nähtävillä täällä.

Metabolix [18.06.2021 23:46:58]

#

Ehdotin aiemmin, että erottaisit laskennan ja käyttöliittymän. Nyt edelleen jokaisessa laskentaan liittyvässä funktiossa on myös käyttöliittymän asioita. Lasket myös samoja asioita kahteen kertaan (mm. osuus ja kumulatiivinen) ja käytät laskuissa globaaleja muuttujia aiheettomasti (erityisesti i, joka on vain taulukon koko!), ja ainakin tulo ja vaesto ovat ilmeisesti käyttämättömiä. Pythonissa voi muuten käyttää myös ä-kirjainta nimissä.

Lisäksi tuollainen for-silmukoiden käyttö summaan ja listan muuttamiseen on kömpelöä, jos voisi käyttää suoraan yhden rivin koodeja:

summa = sum(tulot)
osuus = [x / summa for x in tulot]

koodaaja [19.06.2021 03:13:53]

#

Seuraavat parannukset tehty: for-silmukat muutettu lambdaksi ja käyttöliittymä ja laskenta on erotettu.

Metabolix [19.06.2021 09:02:53]

#

Hienosti löysit myös accumulate-funktion, jota en itse muistanut suoraan. Eli ilmeisesti taitoa löytyisi, kun viitsisit keskittyä ja miettiä asiat alusta loppuun. Voisit tehdä paljon hyödyllisempiä vinkkejä ja kehittyä samalla itse enemmän, kun yrittäisit tehdä oikein selvää ja huolellista koodia.

Kommentoin jo turhista muuttujista, vieläkin näitä turhia rivejä vilisee:

# ei käytetä:
tulo = 1
i = 0

# aina tosi, ja vaikka ei olisi, ihan sama:
if (len(tulot) > 0):

# turhia sijoituksia, kun arvot korvataan heti:
summa = 0.0
# summa = sum(tulot)
pinta_ala = []
# pinta_ala = list(map(...))

Voisit supistaa pinta-alan kaavaa, kun et tarvitse alaa taulukkona. Tuostahan tulee vain:

pa = (sum(kumulatiivinen) - kumulatiivinen[-1]/2) / i

Voisit vielä poistaa tulot-taulukosta ylimääräisen nollan alusta. Sehän on virhe, ylimääräinen datapiste. Missään nimessä laskenta-funktion ei pitäisi muuttaa annettua dataa ulos päin, se on yllättävä vaikutus ja huono toimintatapa. Ilmeisesti olet laittanut nollan, jotta pinta-alan lasku toimisi oikein, mutta yllä olevalla kaavan päivityksellä nollaa ei enää tarvita, tai jos haluat sen, voit lisätä sen kumulatiivisen listan alkuun.

kumulatiivinen = [0] + list(...)

Tuo i-nimi on hieman häiritsevä, ei kovin kuvaava tuossa käytössä, yleisesti käytössä indeksin merkkinä. Jos poistat ylimääräisen nollan tuloista, i on len(tulot) ja voit luopua tästäkin ylimääräisestä muuttujasta. Tai kuvaava nimi olisi vaikka (populaation) koko.


Sivun alkuun

Vastaus

Muista lukea kirjoitusohjeet.
Tietoa sivustosta