Data Science meets Software Data

Jena, 15.05.2019

Markus Harrer, Software Development Analyst

Twitter: @feststelltaste
Blog: feststelltaste.de

Data Science

Was ist Data Science?

"Datenanalysen auf nem Mac."


Frei nach https://twitter.com/cdixon/status/428914681911070720

Meine Definition von Data Science

Was bedeutet "data"?

"Without data you‘re just another person with an opinion."

W. Edwards Deming

=> Belastbare Erkenntnisse mittels Fakten liefern

Was bedeutet "science"?

"The aim of science is to seek the simplest explanations of complex facts."

Albert Einstein

=> Neue Erkenntnisse verständlich herausarbeiten

Was ist ein Data Scientist?

"Jemand, der mehr Ahnung von Statistik
  hat als ein Softwareentwickler
  und mehr Ahnung von Softwareentwicklung
  als ein Statistiker."

Nach zu https://twitter.com/cdixon/status/428914681911070720

Data Science: Perfect match!

Welche Daten sollen wir analysieren?

Softwaredaten!

Alles was aus der Entwicklung und dem Betrieb der Softwaresysteme so anfällt:

  • Statische Daten
  • Laufzeitdaten
  • Chronologische Daten
  • Daten aus der Software-Community

Zwischenfazit

Data Science Software Data = Software Analytics

Wie Analysen umsetzen?

Der Leitgedanke

=> von der Frage über die Daten zur Erkenntnis!

[(Daten + Code + Ergebnis) pro gedanklichen Schritt] + komplette Automatisierung

Schlüsselelement: Computational Notebooks

Der Computational Notebook Ansatz


Technologie (1/2)

Klassischer Data-Science-Werkzeugkasten

  • Jupyter
  • Python 3
  • pandas
  • matplotlib

Technologie (2/2)

Jupyter funktioniert und integriert sich auch mit

  • Cypher / Neo4j / jQAssistant
  • JVM-Sprachen über beakerx / Tablesaw
  • bash
  • ...

Beispiele für gezielte Datenanalysen

  • Performance-Bottlenecks ausfindig machen
  • Chronische Architektur-/Design-/Code-Smells entdecken
  • No-Go-Areas in Altanwendungen identifizieren
  • Änderungsverhalten in Teams analysieren
  • ...

Erstes Hands-On

Der Patient

DropOver

  • Web-Anwendung zur Organisation von Partys
  • Veranstaltungsseite, Kommentare, Terminplanung, Foto-Upload, ...
  • Java EE, Clean Code, starke Modularisierung
  • Entwickelt von drei Leuten Teams

I. Idee (1/3)

Schildert Ausgangslage, Analyseideen und Heuristiken.

Fragen

  1. Gibt es Module mit Teammonopole?
  2. Wie gut passt die Modularisierung zum Team?
  3. Wie sieht eine alternative Modularisierung aus?

I. Idee (2/3)

Umsetzung

  • Datenquelle: Git & Excel-Sheet
  • Werkzeuge: Jupyter, pandas, Visualisierungen, Low-End Machine Learning (MDS, AHC)

Meta-Ziel: Alles mal sehen anhand eines einfachen Show-Cases.

I. Idee (3/3)

Frage 1: Gibt es Module mit Teammonopole?

Unsere Heurisik: **Ändert** **ein Team** hauptsächlich Module?

II. Laden

  • Mögliche Datenquelle: Git
  • Helferlein: Open Zippy Analysis Platform For Data In Software

In [1]:
from ozapfdis import git
log = git.log_numstat("../../../dropover/")
log.head(3)


Out[1]:
additions deletions file sha timestamp author
0 191.0 0.0 backend/pom-2016-07-16_04-40-56-752.xml 8c686954 2016-07-22 17:43:38 Michael
1 1.0 1.0 backend/src/test/java/at/dropover/scheduling/i... 97c6ef96 2016-07-16 09:51:15 Markus
2 19.0 3.0 backend/src/main/webapp/app/widgets/gallery/js... 3f7cf92c 2016-07-16 09:07:31 Markus

Was haben wir hier eigentlich?


In [2]:
log.info()


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2403 entries, 0 to 2402
Data columns (total 6 columns):
additions    2353 non-null float64
deletions    2353 non-null float64
file         2403 non-null object
sha          2403 non-null object
timestamp    2403 non-null datetime64[ns]
author       2403 non-null object
dtypes: datetime64[ns](1), float64(2), object(3)
memory usage: 112.7+ KB

1 DataFrame (~ programmierbares Excel-Arbeitsblatt), 6 Series (= Spalten), 2403 Rows (= Einträge)

III. Filtern

  • Daten sind oft zu viel und durchwachsen
  • Filtern hilft beim Eingrenzen

Wir machen nur mit Java-Produktionscode weiter


In [3]:
java = log.copy()
java = java[java['file'].str.startswith("backend/src/main/java")]
java = java[~java['file'].str.contains("package-info.java")]
java.head()


Out[3]:
additions deletions file sha timestamp author
4 3.0 4.0 backend/src/main/java/at/dropover/files/intera... ec85fe73 2016-07-16 08:12:29 Chris
36 3.0 2.0 backend/src/main/java/at/dropover/scheduling/i... bfea33b8 2016-07-16 02:02:02 Markus
44 23.0 1.0 backend/src/main/java/at/dropover/scheduling/i... ab9ad48e 2016-07-16 00:50:20 Chris
47 68.0 6.0 backend/src/main/java/at/dropover/files/intera... 0732e9cb 2016-07-16 00:27:20 Chris
53 7.0 3.0 backend/src/main/java/at/dropover/framework/co... ba1fd215 2016-07-15 22:51:46 Chris

IV. Anreichern

  • Oft werden Daten
    • aus bestehenden Daten extrahiert
    • aus anderen Quellen mit verschnitten

=> Mehr Perspektiven auf ein Problem möglich

Wir extrahiere Modulnamen aus den Dateinamen.


In [4]:
java['module'] = java['file'].str.split("/").str[6]
java.head()


Out[4]:
additions deletions file sha timestamp author module
4 3.0 4.0 backend/src/main/java/at/dropover/files/intera... ec85fe73 2016-07-16 08:12:29 Chris files
36 3.0 2.0 backend/src/main/java/at/dropover/scheduling/i... bfea33b8 2016-07-16 02:02:02 Markus scheduling
44 23.0 1.0 backend/src/main/java/at/dropover/scheduling/i... ab9ad48e 2016-07-16 00:50:20 Chris scheduling
47 68.0 6.0 backend/src/main/java/at/dropover/files/intera... 0732e9cb 2016-07-16 00:27:20 Chris files
53 7.0 3.0 backend/src/main/java/at/dropover/framework/co... ba1fd215 2016-07-15 22:51:46 Chris framework

Wir ordnen bei uns Autoren zu Teams zu.

Schritt 1: Weitere Datenquelle einlesen.


In [5]:
import pandas as pd
orga = pd.read_excel("../dataset/Teamorganisation.xlsx", index_col=0)
orga


Out[5]:
team
name
Markus A
Chris B
Michael C

Wir ordnen bei uns Committer zu Teams zu.

Schritt 2: Datenquellen joinen


In [6]:
java = java.join(orga, on='author')
java.head()


Out[6]:
additions deletions file sha timestamp author module team
4 3.0 4.0 backend/src/main/java/at/dropover/files/intera... ec85fe73 2016-07-16 08:12:29 Chris files B
36 3.0 2.0 backend/src/main/java/at/dropover/scheduling/i... bfea33b8 2016-07-16 02:02:02 Markus scheduling A
44 23.0 1.0 backend/src/main/java/at/dropover/scheduling/i... ab9ad48e 2016-07-16 00:50:20 Chris scheduling B
47 68.0 6.0 backend/src/main/java/at/dropover/files/intera... 0732e9cb 2016-07-16 00:27:20 Chris files B
53 7.0 3.0 backend/src/main/java/at/dropover/framework/co... ba1fd215 2016-07-15 22:51:46 Chris framework B

V. Aggregieren

  • Vorhandene Daten sind oft zu viel für manuelle Sichtung
  • Neue Einsichten über Problem aber oft auf hoher Flugbahn möglich

= > Feingranulare Daten sinnvoll zusammenfassen

Wir zählen die Änderungen aller Klassen pro Modul und Team.


In [7]:
changes = java.groupby(['module', 'team'])[['sha']].count()
changes.head()


Out[7]:
sha
module team
comment A 41
B 76
C 1
creator A 14
B 33

Wir berechnen alle erfolgten Änderungen pro Modul...


In [8]:
changes['all'] = changes.groupby('module').transform('sum')
changes.head()


Out[8]:
sha all
module team
comment A 41 118
B 76 118
C 1 118
creator A 14 54
B 33 54

...und damit die Änderungsverhältnisse pro Team.


In [9]:
changes['ratio'] = changes['sha'] / changes['all']
changes.head()


Out[9]:
sha all ratio
module team
comment A 41 118 0.347458
B 76 118 0.644068
C 1 118 0.008475
creator A 14 54 0.259259
B 33 54 0.611111

IV. Visualisieren

  • Grafische Darstellung geben Analysen den letzten Schliff
  • Probleme können Außenstehenden visuell dargestellt besser kommuniziert werden

Wir bauen uns ein Balkendiagramm mit den Verhältnissen der Teamänderungen.


In [10]:
changes['ratio'].unstack().plot.bar(stacked=True);


Ende Hands-On

Meine Schätzung

Wir haben jetzt 80% der notwendigen Befehle für Datenanalysen in der Softwareentwicklung abgedeckt!

Fortgeschrittenes Click-Through!

Frage 2: Wie gut passt die Modularisierung zur Arbeitweise der Teams?

Unsere Heuristik: Werden fachliche Komponenten **zusammengehörig geändert**?

Unsere Ausgangsbasis


In [11]:
java.head()


Out[11]:
additions deletions file sha timestamp author module team
4 3.0 4.0 backend/src/main/java/at/dropover/files/intera... ec85fe73 2016-07-16 08:12:29 Chris files B
36 3.0 2.0 backend/src/main/java/at/dropover/scheduling/i... bfea33b8 2016-07-16 02:02:02 Markus scheduling A
44 23.0 1.0 backend/src/main/java/at/dropover/scheduling/i... ab9ad48e 2016-07-16 00:50:20 Chris scheduling B
47 68.0 6.0 backend/src/main/java/at/dropover/files/intera... 0732e9cb 2016-07-16 00:27:20 Chris files B
53 7.0 3.0 backend/src/main/java/at/dropover/framework/co... ba1fd215 2016-07-15 22:51:46 Chris framework B

Wir zeigen für jeden Commit die geänderten Dateien.


In [12]:
java['changed'] = 1 # markiere Änderungen
commit_matrix = java.pivot('file', 'sha', 'changed').fillna(0)
commit_matrix.iloc[:5,50:55]


Out[12]:
sha 3597d8a2 3b70ea7e 3d3be4ca 3e4ae692 429b3b32
file
backend/src/main/java/at/dropover/comment/boundary/AddCommentRequestModel.java 0.0 0.0 0.0 0.0 0.0
backend/src/main/java/at/dropover/comment/boundary/ChangeCommentRequestModel.java 0.0 0.0 0.0 1.0 0.0
backend/src/main/java/at/dropover/comment/boundary/CommentData.java 0.0 0.0 0.0 1.0 0.0
backend/src/main/java/at/dropover/comment/boundary/GetCommentRequestModel.java 0.0 0.0 0.0 0.0 0.0
backend/src/main/java/at/dropover/comment/boundary/GetCommentResponseModel.java 0.0 0.0 0.0 0.0 0.0

Wir berechnen den Abstand zwischen den vorgenommmenen Commits pro Datei (=Vektor)...


In [13]:
from sklearn.metrics.pairwise import cosine_distances

dis_matrix = cosine_distances(commit_matrix)
dis_matrix[:5,:5]


Out[13]:
array([[0.        , 0.29289322, 0.5       , 0.18350342, 0.29289322],
       [0.29289322, 0.        , 0.29289322, 0.1339746 , 0.5       ],
       [0.5       , 0.29289322, 0.        , 0.59175171, 0.29289322],
       [0.18350342, 0.1339746 , 0.59175171, 0.        , 0.42264973],
       [0.29289322, 0.5       , 0.29289322, 0.42264973, 0.        ]])

...und machen die Darstellung schöner...


In [14]:
class_name = commit_matrix.index.str.split("/").str[-1].str.split(".").str[0]
dis_df = pd.DataFrame(dis_matrix, class_name, class_name)
dis_df.iloc[:5,:2]


Out[14]:
file AddCommentRequestModel ChangeCommentRequestModel
file
AddCommentRequestModel 0.000000 0.292893
ChangeCommentRequestModel 0.292893 0.000000
CommentData 0.500000 0.292893
GetCommentRequestModel 0.183503 0.133975
GetCommentResponseModel 0.292893 0.500000

Wir brechen nun die Matrix auf 2D herunter


In [15]:
from sklearn.manifold import MDS

model = MDS(dissimilarity='precomputed', random_state=0)
dis_2d = model.fit_transform(dis_df)
dis_2d_df = pd.DataFrame(dis_2d, commit_matrix.index, ["x", "y"])
dis_2d_df.head(3)


Out[15]:
x y
file
backend/src/main/java/at/dropover/comment/boundary/AddCommentRequestModel.java -0.525928 0.450702
backend/src/main/java/at/dropover/comment/boundary/ChangeCommentRequestModel.java -0.568260 0.215280
backend/src/main/java/at/dropover/comment/boundary/CommentData.java -0.527468 0.347568

Wir fügen eine Sicht auf die Module hinzu.


In [16]:
dis_2d_df['module'] = dis_2d_df.index.str.split("/").str[6]
dis_2d_df.head()


Out[16]:
x y module
file
backend/src/main/java/at/dropover/comment/boundary/AddCommentRequestModel.java -0.525928 0.450702 comment
backend/src/main/java/at/dropover/comment/boundary/ChangeCommentRequestModel.java -0.568260 0.215280 comment
backend/src/main/java/at/dropover/comment/boundary/CommentData.java -0.527468 0.347568 comment
backend/src/main/java/at/dropover/comment/boundary/GetCommentRequestModel.java -0.558567 0.262028 comment
backend/src/main/java/at/dropover/comment/boundary/GetCommentResponseModel.java -0.403657 0.498037 comment

Wir erzeugen uns eine interaktive Grafik.
Helferlein: An Unifying Software Integrator


In [17]:
from ausi import pygal
xy = pygal.create_xy_chart(dis_2d_df, "module")
xy.render_in_browser()


file://C:/Users/MARKUS~1/AppData/Local/Temp/tmp_r7if0oj.html

Beyond Hands-On!

Frage 3: Gibt es eine alternative Modularisierung?

Unsere Heuristik: Wie würde sich das System rein nach seinen **Änderungen** strukturieren?

Unsere Ausgangsbasis


In [18]:
commit_matrix.iloc[:5,50:55]


Out[18]:
sha 3597d8a2 3b70ea7e 3d3be4ca 3e4ae692 429b3b32
file
backend/src/main/java/at/dropover/comment/boundary/AddCommentRequestModel.java 0.0 0.0 0.0 0.0 0.0
backend/src/main/java/at/dropover/comment/boundary/ChangeCommentRequestModel.java 0.0 0.0 0.0 1.0 0.0
backend/src/main/java/at/dropover/comment/boundary/CommentData.java 0.0 0.0 0.0 1.0 0.0
backend/src/main/java/at/dropover/comment/boundary/GetCommentRequestModel.java 0.0 0.0 0.0 0.0 0.0
backend/src/main/java/at/dropover/comment/boundary/GetCommentResponseModel.java 0.0 0.0 0.0 0.0 0.0

Wir nutzen hierarchisches Clustering, um anhand der Änderungsmuster alternative Modulestrukturen zu erkennen...


In [19]:
from sklearn.cluster import AgglomerativeClustering

clustering = AgglomerativeClustering()
model = clustering.fit(commit_matrix)
model


Out[19]:
AgglomerativeClustering(affinity='euclidean', compute_full_tree='auto',
            connectivity=None, linkage='ward', memory=None, n_clusters=2,
            pooling_func='deprecated')

...und visualisieren das Ergebnis.


In [20]:
from ausi.scipy import plot_dendrogram
plot_dendrogram(model, labels=commit_matrix.index)


https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/062-exploding-head.svg/600px-062-exploding-head.svg.png

Ausblick

Abhängigkeitsanalyse nach EVA-Prinzip mit OZAPFDIS, pandas und AUSI

Oder auch: Softwarewelt -> Data Science Welt -> Softwarewelt

Zusammenfassung

1. Es gibt unglaublich viele Quellen für Analysen

2. Problemanalysen mit Standard-Data-Science-Werkzeugen einfach möglich

3. Wer mehr will, bekommt auch mehr!


=> von der Frage über die Daten zur Erkenntnis!

Literatur

  • Adam Tornhill: Software Design X-Rays
  • Wes McKinney: Python For Data Analysis
  • Leek, Jeff: The Elements of Data Analytic Style
  • Tim Menzies, Laurie Williams, Thomas Zimmermann: Perspectives on Data Science for Software Engineering

Vielen Dank! Fragen?

Markus Harrer
innoQ Deutschland GmbH

E-Mail: markus.harrer@innoq.com
Twitter: @feststelltaste
Blog: feststelltaste.de