Arbeitszeit zwischen den Zeilen

Veröffentlicht am

Die Idee für dieses Projekt entstand, während ich meinen Data-Science-Kurs absolvierte. Mehrere Ansätze kamen zusammen: Mich reizt es als Datenwissenschaftler natürlich, irgendetwas Neues herauszufinden oder wenigstens zu visualisieren. Sprache lässt sich schon mit einfachen Mitteln auswerten – diese Software läuft auch auf einem Laptop. Und ich wollte etwas Greifbares aus dem Arbeitsalltag ausbauen, das mir selbst neue Erkenntnisse verschafft und auch für Kollegen außerhalb des Data-Science-Kreises interessant ist.

Ich habe also den Anwesenheits-Chat meines letzten Arbeitgebers analysiert. Der Zeitraum reicht über ein Jahr. In diesem Chat haben sich – ursprünglich wegen der Verschiebung ins Home-Office durch Covid – die Mitarbeiterinnen und Mitarbeiter zur Arbeit gemeldet. So konnte jeder im Team schnell erkennen, wer gerade erreichbar ist. Die Mitarbeitenden liefern hier regelmäßig und aus freien Stücken unstrukturierte Informationen. Die Aufgabe ist, diesen Text in ein nutzbares Format zu bringen.

Die Hauptfragen, die ich mir gestellt hatte, waren:

  • Wie stark unterscheiden sich die Anwesenheitsprofile der Mitarbeiter untereinander?
  • Kann man aus dem Chat die üblichen „Verfügbarkeitszeiten“ herausfinden?
  • Wer hat den längsten Arbeitstag?
  • Welche Muster ergeben sich, wenn man die Daten visualisiert?

Die Daten unterscheiden sich von einer reinen Zeiterfassung. Der Chat ist locker, und die Einteilung der Arbeitszeit ist für jeden eigenverantwortlich. Deshalb kommen hier sehr unterschiedliche Charaktere raus.


Datenschutz

Alle Namen von Personen, Organisationen und Projekten sind Pseudonyme. Ich habe mir Personas ausgedacht und ihnen generierte Gesichter gegeben, damit es greifbarer wird. Um Rückschlüsse zu vermeiden, habe ich Nachrichten nach der Analyse zensiert. Außerdem wird bewusst auf den Einsatz eines LLM verzichtet, sodass die Daten das Projektumfeld niemals verlassen.

Wieviele Nachrichten von welchem Mitarbeiter


Daten und Kategorien

Der Datensatz umfasst 3778 Chatnachrichten von zwölf Personen über genau ein Jahr. Ich habe den Chat als HTML gesichert und die Nachrichten, User-Namen und Uhrzeiten ausgelesen.

In einem langen iterativen Prozess habe ich folgende Kategorien definiert. In diese Kategorien (oder auch „Labels“) sollen der Algorithmus die Nachrichten automatisch einsortieren:

  • Anmeldung: Die erste Verfügbarkeit des Tages.
  • Abmeldung: Jemand hat die Arbeit für den Tag endgültig beendet.
  • Zurückmeldung: Jemand meldet sich nach einer Pause zurück, oder deutet an, dass er oder sie zuvor nicht erreichbar war.
  • Pause: Jemand meldet sich ab, mit der Aussicht, sich am selben Tag wiederzumelden.
  • Krank: Jemand ist krankgemeldet oder gesundheitlich nur eingeschränkt verfügbar.
  • Arzt: Jemand meldet, dass er beim Arzt ist.
  • Information: Alle anderen Nachrichten, wie z.B. Smalltalk, Ankündigungen, Hinweise auf zukünftige Verfügbarkeit. Nichts hiervon sagt etwas über die Verfügbarkeit zum Zeitpunkt der Nachricht aus.

Die Analyse soll insgesamt in folgenden Schritten ablaufen:

  • Datensammlung
  • Pseudonymisierung
  • Embedding aller Nachrichten
  • Clustering möglicher „Bedeutungsbereiche“
  • Manuelles Labeln weniger repräsentativer Nachrichten
  • Label Spreading zur Klassifizierung aller Nachrichten
  • Optional: Training eines Klassifikators für zukünftige Chats

Embedding und Priming

Embedding gibt Texten einen „Wert“, den ein Computer mit anderen Texten vergleichen kann. Beispiel: Man könnte Texte danach einsortieren, ob sie negativ oder positiv wirken. „Pistole“ entspricht dann vielleicht einer Null und „Blume“ einer Eins. Diese eine Dimension steht dafür, wie positiv der Text ist – das hilft schon einmal, aber nicht viel.

In der Praxis macht man das nicht entlang einer Achse, sondern entlang vieler hundert Achsen. Es gibt schon fertig verfügbare Algorithmen, die anhand riesiger Textmengen trainiert wurden. Sie erstellen die Werte vereinfacht gesagt anhand dessen, welche anderen Worte in der Nähe vorkommen. So entsteht für jeden Text ein Vektor – eine Sammlung vieler Werte. In diesem Projekt wird jede Nachricht zu 768 Zahlen. Welcher dieser Werte welcher Bedeutung entspricht, ist irrelevant. Wichtig ist, dass ähnliche Bedeutungen auch ähnliche Embeddings haben.

Man stelle sich einfach vor, jedem Text wird ein fester Ort auf einer Landkarte zugewiesen. In den verschiedenen Ländern sammeln sich ähnliche Themen. Der einzige Unterschied: Es ist nicht zweidimensional wie eine Karte, sondern hat eben eine Menge mehr Dimensionen.

Kurz noch zu Priming: Das verwendete Modell ist kein LLM. Es ist also um ein Vielfaches „dümmer“ als ChatGPT, Gemini oder Claude, weil es nur eine statistische Zusammenfassung erstellt und selbst keine Texte vorhersagen kann. Es kann aber über diese Vektorisierung eine gewisse Bedeutung aus der Wortreihenfolge und ihrem Kontext ableiten.

Hier kommt Priming ins Spiel. Statt dem Modell einfach „Bin da“ zur Auswertung zu geben, gibt man ihm einen Rahmensatz mit: „In einem Firmenchat sagt ein Nutzer »Bin da« als Anwesenheits-Status.“ Dadurch sollten mehrdeutige Nachrichten ein besseres Embedding für unseren Zweck bekommen.

Warum nicht einfach nach Worten suchen

Man könnte argumentieren, das sei alles nicht nötig – man kann ja einfach nach „krank“ oder „Pause“ suchen. „War gestern krank“ hat aber eine andere Bedeutung als „Bin heute krank“. Genauso bei: „Pause vorbei“, „Heute leider keine Pause“ und „Gehe jetzt in die Pause“. Diese Nachrichten müssen in verschiedene Kategorien fallen. Ein einfaches Suchen wäre also nicht zielführend. Das Embedding macht diesen Unterschied. Der Vektorisierer erkennt hier Nuancen, abgeleitet aus statistischen Zusammenhängen.


Uhrzeit als Hilfestellung

Um den späteren Algorithmen mehr Hinweise zu geben, übergebe ich ihnen nicht nur die Embeddings der Nachrichten, sondern auch die Uhrzeit, zu der sie gesendet wurden. Bei der Einordnung in „Anmeldung“ oder „Abmeldung“ hilft sie schließlich auch: Nachrichten spät am Tag sind wahrscheinlich Abmeldungen, früh am Tag eher Anmeldungen.

In der Praxis nennt man so ein Feature einen Tie-Breaker. Er entscheidet bei einem Unentschieden: „Bin raus“ muss in eine andere Kategorie fallen, wenn es mittags geschrieben wird (Pause), als wenn es abends geschrieben wird (Abmeldung). Das Embedding ist nämlich dasselbe, deshalb braucht der Algorithmus eine Entscheidungshilfe. Mithilfe der Uhrzeit kann er also die richtige Kategorie finden.

Ich habe den Einfluss dieses Tie-Breakers bewusst klein gehalten, damit der Algorithmus nicht stumpf nach Uhrzeit sortiert. Er soll das Zünglein an der Waage sein, wenn die Embeddings nah beieinander liegen, aber dennoch in unterschiedliche Kategorien fallen sollen.


Wie die Labels entstehen

Jetzt kommen wir zum Kern. Es gibt auf der einen Seite ja die vorher angesprochenen Kategorien (auch Labels genannt) und auf der anderen Seite die Chatnachrichten mit ihren Vektoren (Embedding und Uhrzeit). Wie kommen die Nachrichten jetzt alle an ihr richtiges Label, ohne, dass man es überall händisch angeben muss?

Das passiert in vier Schritten:

  1. Nachrichten gruppieren
  2. Repräsentative Nachrichten für jede Gruppe finden
  3. Diese einzelnen Nachrichten manuell labeln
  4. Den Algorithmus die Nachrichten in ihrer Nähe ähnlich klassifizieren lassen

Durch das (mehrsprachige) Embedding befinden sich Aussagen wie „Ciao“, „Wiedersehen“, „Bye“ und „Au revoir“ ungefähr an einem Punkt. Ebenso verhält es sich für „Pause“, „Kurz weg“ und „Gleich zurück“ und so weiter.

Wenn ich für manche Nachrichten schon wüsste, in welche Kategorie sie fallen, könnte ich die drumrumliegenden Nachrichten in dieselbe Kategorie einsortieren. Das ist Schritt vier, das „Label Spreading“. Man kann es sich vorstellen wie einen Filzstift, der die Farbe der Kategorie hat, und den man bei jedem „repräsentativen“ Begriff auf das Papier drückt. Die Farbe – also die Bedeutung – breitet sich allmählich aus.

Embeddings der Nachrichten

Vereinfachte Darstellung (Vektoren in UMAP 2D projiziert)

1. Gruppieren

Wenn ich zufällige Nachrichten zum Labeln aussuchen würde, übersehe ich vielleicht seltene Kategorien. Wie komme ich dann an die repräsentativsten Nachrichten? Indem ich die Nachrichten zuerst anhand ihrer Vektoren gruppiere. Vereinfacht gesagt: Welche Nachrichten bilden „Wolken“ dadurch, dass sie sich ähneln? In diesem Projekt ergeben sich rund 40 Wolken.

Embeddings nach Nähe gruppiert

K-Means-Clustering in 10 Cluster

Cluster 0:
Pausi

Cluster 1:
Bin raus
und raus
erstmal raus
Kurz mal raus
kurz weg

Cluster 2:
morgen
Good morning

Cluster 3:
ups ja ich auch
tschüssikowski
ich auch
Same here
Cu

Cluster 4:
Bin back
Back
zurück

Cluster 5:
(Raus für heute)
bis morgen
Raus für heute

Cluster 6:
Me kurz
bin da
Wieder zurück
Bin unterwegs
nicht mehr unterwegs
Unterwegs
Wieder da
Bin weg kurz
Auch wieder da

Cluster 7:
Ich jetzt auch
Sara and I lunch

Cluster 8:
Guten Morgen

Cluster 9:
Hi
Hallo
Wechsel ins Büro
bye bye
bye
Hallo aus dem Office

2. Repräsentative Nachrichten finden

Die repräsentativsten Nachrichten liegen nun wahrscheinlich in der Mitte einer Wolke, weil sie dort am weitesten weg sind von den anderen Begriffswolken. Diese Mittel-Nachrichten sind die ersten, die ich labele.

Cluster 0:
Pausi ➞ Pause

Cluster 1:
Bin raus ➞ Pause

Cluster 2:
morgen ➞ Anmeldung

Cluster 3:
Same here ➞ Information

Cluster 4:
Back ➞ Zurückmeldung

Cluster 5:
bis morgen
➞ Abmeldung

Cluster 6:
Wieder da ➞ Zurückmeldung

Cluster 7:
Sara and I lunch ➞ Information

Cluster 8:
Guten Morgen ➞ Anmeldung

Cluster 9:
Hallo aus dem Office ➞ Anmeldung

Kleiner Trick: Ich labele nicht nur die mittlere Nachricht jeder Wolke (wie hier gezeigt), sondern auch zwei oder drei am Rand. Dadurch erkennt der spätere Algorithmus die Grenzen zwischen Bedeutungen besser. Außerdem könnten Randpunkte fälschlicherweise in dieser Wolke gelandet sein – das lässt sich so korrigieren.

3. Manuelles Labeln

Bei 40 Gruppen mit jeweils vier Punkten haben wir 160 zu labelnde Aussagen. Das ist machbar – wir müssen keine 3778 Nachrichten einsortieren. Damit es ein bisschen schneller geht, habe ich mir ein kleines Python-Script geschrieben, das im Terminal sehr einfach das Labeln dieser vorausgewählten Punkte ermöglicht. Ich musste nur den richtigen Button drücken, und der nächste Begriff taucht auf.

4. Label Spreading

Jetzt haben wir den Filzstift aufgesetzt. Die Ähnlichkeit der Nachrichten zueinander bestimmt nun, wie weit sich ein zugewiesenes Label in die Nachbarschaft ausbreitet. Wir geben also nicht einfach der ganzen Wolke das gleiche Label, sondern die Nachbarschaft und die Dichte der Punkte an der Stelle entscheiden, wie stark sich das Label ausbreitet. Die Wolken spielen hier keine Rolle mehr. Nach und nach bekommen so alle Punkte ein Label.

Um das Ergebnis noch präziser zu machen, gibt das Label Spreading eine Konfidenz zu jedem zugewiesenen Label aus. Das ist ein statistischer Wert dafür, wie fundiert die Annahme ist, die der Algorithmus trifft. Ich lasse mir die Nachrichten mit der geringsten Konfidenz ausgeben und nehme sie in die Gruppe der manuell zu labelnden Nachrichten auf, und: gebe auch diesen ein Label per Hand. So steigt die Zuverlässigkeit des Algorithmus schnell an. Das lässt sich so lange wiederholen, bis eine akzeptable Gesamtzuversicht erreicht ist.


Bewertung

Wie „richtig“ ist das Ergebnis? Es gibt verschiedene Methoden, das zu prüfen, aber in diesem Fall: Keine definitive. Stichproben sind mühselig und wenig aussagekräftig.

Ich habe mir die Nachrichten als Punkte auf einer interaktiven 24-Stunden-Uhr ausgeben lassen. Pro Kategorie eine Farbe; wenn ich mit dem Mauszeiger über einen Punkt fahre, sehe ich die jeweilige Nachricht und ihre Kategorie. So bekomme ich schnell ein Gefühl dafür, ob das Ergebnis stimmt.

Interaktive Übersicht zum Prüfen

Mathematisch lässt sich hier z.B. mit dem Davies-Bouldin-Index prüfen, wie gut jede Kategorie sich gegen die Nachbarn abgrenzt – je niedriger der Wert, desto besser. Allerdings zeigt er nur, wie gut sich die Gruppen mathematisch unterscheiden, nicht ob die Trennung sinnvoll ist. Wer wirklich hohe Konfidenz braucht, muss alle Nachrichten von Hand labeln.


Ergebnisse

Für diese Analyse wurden 140 von 3778 Nachrichten händisch gelabelt. Das sind ungefähr 3,7 % – im Schnitt reicht also eine händisch klassifizierte Nachricht aus, um 26 andere automatisch zu klassifizieren.

Trick: Ich habe das Label einer Nachricht für alle gleichen übernommen, und dabei Groß-Kleinschreibung ignoriert. So ließ sich der Anteil mit definitivem Label noch erhöhen.

z.B. „Guten Morgen” ➞ „Anmeldung“
gilt dann auch für
„guten morgen“,
„guten Morgen“,
„GUTEN MORGEN“
usw.)

Verteilung

Welcher Mitarbeiter hat welchen Anteil an den Kategorien

Muster

Ein interessantes Bild ergibt sich, wenn man die Nachrichtentypen pro Person aufschlüsselt. Man sieht gleich, wer geregelte Arbeitszeiten hat und wer flexibel ist, und wie lang die Arbeitstage üblicherweise sind.

Isabel Becker

Kategorie und PeakTypische Nachricht
Anmeldung:
07:04 Uhr
Guten Morgen
Pause:
10:28 Uhr
Unterwegs
Abmeldung:
17:01 Uhr
Bin raus

Basti Eder

Kategorie und PeakTypische Nachricht
Anmeldung:
07:28 Uhr
Guten Morgen
Pause:
09:13 Uhr
Bin unterwegs
Abmeldung:
16:32 Uhr
Bin raus

Simon Forsch

Kategorie und PeakTypische Nachricht
Anmeldung:
07:58 Uhr
Hello
Pause:
10:28 Uhr
Erstmal unterwegs
Abmeldung:
19:26 Uhr
Ciao

Steven Zeller

Kategorie und PeakTypische Nachricht
Anmeldung:
08:18 Uhr
morgen
Pause:
10:08 Uhr
ach ja, wieder da
Abmeldung:
17:16 Uhr
bis morgen

Julian Vollrath

Kategorie und PeakTypische Nachricht
Anmeldung:
09:08 Uhr
Good morning
Pause:
13:32 Uhr
Jetzt was essen
Abmeldung:
18:56 Uhr
Bis morgen!

Soraya Rostami

Kategorie und PeakTypische Nachricht
Anmeldung:
09:13 Uhr
Guten Morgen
Pause:
12:47 Uhr
Bin was Essen
Abmeldung:
14:37 Uhr
Bin für heute raus

Mark Kraft

Kategorie und PeakTypische Nachricht
Anmeldung:
09:23 Uhr
hi
Pause:
12:02 Uhr
Wechsel ins Büro
Abmeldung:
19:26 Uhr
Cu

Sara Melnyk

Kategorie und PeakTypische Nachricht
Anmeldung:
09:28 Uhr
Hi
Pause:
13:37 Uhr
Pausi
Abmeldung:
17:51 Uhr
byeeeee

Stefan Schiwietz

Kategorie und PeakTypische Nachricht
Anmeldung:
09:33 Uhr
Hallo aus dem Office
Pause:
12:37 Uhr
Pausi
Abmeldung:
16:47 Uhr
Raus für heute

Maya Rawlinson

Kategorie und PeakTypische Nachricht
Anmeldung:
09:33 Uhr
hello
Pause:
12:32 Uhr
mittag
Abmeldung:
15:37 Uhr
Tschüssi

Anna-Lena Vogt

Kategorie und PeakTypische Nachricht
Anmeldung:
09:43 Uhr
hallo aus dem Büro 🙂
Pause:
13:47 Uhr
kurze Mittagspause 🙂
Abmeldung:
16:02 Uhr
raus für heut 🙂

Wann sich welche Meldungen häufen

Verteilt man die Kategorien über den Tag, sieht man, wann sich Anmeldungen, Pausen oder Abmeldungen häufen. Über das Maximum der Dichteverteilung lassen sich die Peaks ablesen.

Überraschungen

Mich hat überrascht, wie unterschiedlich die Startzeiten der Mitarbeiter sind. Im Alltag ist mir nie aufgefallen, dass jemand besonders früh oder besonders spät eingecheckt hätte – es hat einfach gut gepasst. Aufgefallen ist auch, dass manche Mitarbeiter überhaupt keine Pause melden. Wahrscheinlich, weil sie sehr geregelte Pausen haben, die mit denen des restlichen Teams übereinstimmen.


Diskussion

Fehlende Ground Truth

Das Hauptproblem ist die fehlende Ground Truth – ein Benchmark, gegen den ich die Klassifikationsgüte prüfen könnte. Ich kann zwar messen, ob Cluster sich gegeneinander gut trennen, aber nicht, ob diese Trennung sinnvoll ist. Stichproben und subjektives Urteilen sind alles, was bleibt. Dadurch wird das Hyperparameter-Tuning (das Einstellen der „Knöpfe“ des Modells) zum Bauchgefühl-Ratespiel. Ich kann zum Beispiel nicht definitiv sagen, ob das Priming einen großen Effekt hatte.

Heute würde ich das methodischer angehen. Ich habe später gelernt, dass man die Qualität von Label Spreading auch ohne Ground Truth prüfen kann: Man lässt einfach Teile der bekannten Labels weg und schaut, wie gut der Algorithmus sie vorhersagt. Damit ließe sich auch testen, ob das Priming einen Unterschied macht.

Lange Nachrichten

Sehr lange Nachrichten landen meistens in der falschen Kategorie. Das hängt nicht direkt mit der Länge zusammen, sondern damit, dass sie verschiedene Sinninhalte kombinieren – die „Richtung“ verwischt. Man könnte das kompensieren, indem man die Nachricht in Sätze zerlegt und nur diejenige Teilnachricht behält, die die höchste Konfidenz erhält.

Moralische Gedanken

Es zeichnen sich je nach Mitarbeiterin oder Mitarbeiter sehr unterschiedliche Profile ab. Manche melden sich so pünktlich und mit so wenig Streuung an und ab, dass der Verdacht aufkommt, ihre Nachrichten könnten automatisiert sein. Andere zeigen so unregelmäßige Profile, dass man auf den ersten Blick an ihrer Zuverlässigkeit zweifeln könnte. Aus persönlicher Erfahrung mit den Mitarbeitenden würde ich aber sagen, dass weder das eine noch das andere der Fall ist. Es sind einfach nur Unterschiede in der Art der Nachrichten.

Aus diesen Profilen auf Verhaltensmuster zu schließen, ist statistisch okay, moralisch aber grenzwertig. Der Chat ist für alle einsehbar, auch rückwirkend. Wenn man die Daten klassifiziert und visualisiert, erhält man keine neuen Informationen – man macht sie nur greifbarer. Trotzdem: Schlüsse auf die Produktivität, die Zuverlässigkeit oder die Ehrlichkeit der Mitarbeiter zu ziehen, halte ich für riskant und würde davon auf jeden Fall abraten. Aus diesem Grund habe ich die Analyse pseudonymisiert und die meisten Nachrichten unkenntlich gemacht.

Bedenklich wäre auch, den Chat als Dokumentation der Arbeitszeit zu nutzen. Er kann lediglich Hinweise geben – denn es ist nur gewünscht, nicht verpflichtend, in den Chat zu schreiben. Niemand hat sich hier durchgehend sauber über alle Arbeitstage hinweg korrekt an- und abgemeldet.

Übertragbarkeit

Da es sich um eine explorative Datenanalyse handelt, ist das Projekt kein sauberes Software-Tool. Die Logik lässt sich aber auf andere Firmenchats übertragen. Aus den trainierten Modelldaten lassen sich keine Originalnachrichten wiederherstellen, weil jede Nachricht ein numerisches Embedding ist – ähnlich wie ein Hash-Code nicht zurückwandelbar.

Ausblick auf weitere Analysen

Bei der Arbeit bin ich auf weitere Ideen gekommen. Spannend wäre etwa herauszufinden, mit welchen Nachrichten sich welche Person typischerweise meldet. Sagt sie morgens immer nur „Hi“? Oder ist sie der „Guten Morgen“-Typ?


Fazit

Dieses Projekt diente mir als Übungsfeld, um abzustecken, wie sehr sich die Klassifikation von Nachrichten ohne Large Language Model automatisieren lässt. Überraschenderweise hat es von Anfang an brauchbar gut funktioniert.

Neu für mich waren Sentence-Transformer und der Label-Spreading-Algorithmus. Ich habe viel über Semi-Supervised Learning gelernt – also unter Vorgabe weniger Beispiele auf die Klasse aller Datenpunkte zu schließen. Das ist nützlich, wenn manuelles Klassifizieren zu zeitaufwendig wäre. Für eine endgültige Anwendung würde man danach noch einen Klassifikationsalgorithmus einbauen.

Nachdem ich die Firma verlassen habe, habe ich das Projekt meinen ehemaligen Kollegen gezeigt – aus Transparenz, und weil sie wissen wollten, womit ich jetzt arbeite.

Für die Zukunft würde ich mich mit Data Augmentation beschäftigen – z.B. wie gut sich mit VAEs die Trainingsdaten anonymisieren lassen.