Redactionele noot: dit bericht is geschreven in samenwerking met Milan van der Meer. Beide auteurs van dit bericht maken deel uit van het team van Real Impact Analytics, een innovatieve Belgische bigdatastartup die de waarde in telecomdata ontsluit door "big data te appificeren".
Deze tutorial geeft je een klein voorproefje van waarom je willekeurige datasets zou willen genereren en wat je ervan kunt verwachten. Ook loop je door de eerste voorbeelden van het gebruik van Trumania, een Python-bibliotheek voor datageneratie.
Voor meer informatie kun je terecht op Trumania's GitHub!
Waarom willekeurige datasets genereren?
Het genereren van willekeurige datasets is relevant voor zowel data engineers als data scientists. Waarom werken data scientists en data engineers met synthetische data en hoe verkrijgen ze die?
Als data engineer, nadat je je nieuwe geweldige dataverwerkingsapplicatie hebt geschreven, denk je dat het tijd is om end-to-end te testen en heb je dus invoergegevens nodig.
Als data scientist profiteer je van datageneratie omdat je er op verschillende manieren mee kunt experimenteren: datasets verkennen, algoritmes testen, technieken voor datavisualisatie uitproberen, of aannames over het gedrag van een methode toetsen tegen veel verschillende datasets naar keuze.
In beide gevallen is het verleidelijk om gewoon echte data te gebruiken. Eén klein probleem: productiedata is doorgaans lastig te bemachtigen, zelfs gedeeltelijk, en met nieuwe Europese wetten rond privacy en security wordt dat er niet makkelijker op.
Misschien ben je een van de gelukkigen en heb je een mooie, complete productiedataset op je laptop. Zelfs dan wil je meer variatie in de dataset om te valideren tegen verschillende situaties, zoals andere populatiegroottes, andere dataverdelingen, uiteenlopende datakwaliteitsproblemen zoals ontbrekende of ongeldige gegevens...
Voor een uitgebreidere uitleg over waarom willekeurige datasets nuttig zijn, lees 'Why synthetic data is about to become a major competitive advantage'.
Schema-gebaseerde willekeurige datageneratie: we hebben goede relaties nodig!
Deze sectie probeert schema-gebaseerde willekeurige datageneratie te illustreren en de beperkingen ervan te tonen.
Er bestaan al veel tools om willekeurige datasets te genereren. Een gangbare aanpak is schema-gebaseerde generatie, waarmee je een blauwdruk definieert en die gebruikt om entiteiten te genereren. Khermes en LogSynth zijn twee voorbeelden van zulke tools.
Een voorbeeld van een schema-configuratie zou er zo uit kunnen zien, met dit persoon-schema:
{
{
"field": "Name",
"class": NameGenerator
},
{
"field": "Age",
"class": RandomInt(18, 65)
},
{
"field": "Gender",
"class": RandomPicker(["Male", "Female", "Other"])
}
}
Dit schema definieert de generatie van wat gegevens over een persoon. Het bevat een naam, leeftijd en geslacht. Het is simpel, snel en heeft ook beperkingen. Een belangrijke beperking is het faken van relaties tussen verschillende entiteiten of op basis van tijd.
Hoe maak je bijvoorbeeld sommige namen waarschijnlijker op basis van geslacht? Of hoe maak je een log van gebruikersacties waarin acties vaker voorkomen tijdens het weekend?
De schema-gebaseerde aanpak maakt het niet eenvoudig om je nieuwe applicatie bloot te stellen aan echte, lastige situaties, zoals:
- Onevenwichtige datasets (moeilijkheden bij group-by-achtige operaties);
- Geklonterde patronen in een entiteitennetwerk. Denk aan het ontdekken van sociale groepen via gebruikersinteracties (bijvoorbeeld, telefoongesprekken);
- Verschillende tijdsprofielen voor activiteit. Acties vinden waarschijnlijker plaats op bepaalde tijden van de dag of week;
- Causale relaties tussen acties. Bijvoorbeeld, veel aankopen in een winkel die uiteindelijk een "out-of-stock"-event triggeren.
Trumania is gebaseerd op scenario’s om deze tekortkomingen aan te pakken en realistischer datasets te genereren. Terwijl het scenario zich ontvouwt, interacteren verschillende populaties met elkaar, werken hun eigenschappen bij en genereren logs.
Een voorbeeldscenario is mensen die hun vrienden bellen. Wanneer iemand belt, betaalt diegene in belminuten; als de minuten op zijn, moet die naar een winkel... De resulterende dataset kan het log van oproepen zijn, het log van winkelacties en het voorraadniveau per uur. Acties in een scenario kunnen willekeurig of deterministisch zijn.
In Trumania zijn de gegenereerde datasets doorgaans tijdreeksen, omdat ze voortkomen uit de uitvoering van een scenario dat zich in de tijd afspeelt.
Onderling gerelateerde data genereren met Trumania (aka Tutorial Time!)
In het onderstaande voorbeeld zie je hoe je een basisscenario uitwerkt waarin mensen elkaar berichten sturen. De resulterende dataset wordt de tijdreeks van berichten die tussen mensen zijn uitgewisseld.
We doorlopen de volgende stappen:
- een Trumania-circus creëren, de wereld waarin het scenario wordt uitgevoerd
- een "hello world"-story toevoegen die basis, hardgecodeerde logs produceert
- een relationship toevoegen om elke persoon favoriete quotes toe te wijzen, die worden gebruikt als inhoud van de berichten
- de tijdsparameters van de story parametriseren voor een realistischer verdeling van berichten over de dag
- nog een relationship toevoegen om een sociaal netwerk te definiëren, zodat de sociale graaf die uit het berichtenlog ontstaat realistischer is
Een Trumania Circus maken
Doel
De eerste stap is het creëren van een circus, de wereld waarin alle elementen van het scenario bestaan. We maken ook een basispopulatie van persons. Omdat veel aspecten van Trumania willekeurig zijn, introduceert deze stap ook het concept generators, die worden gebruikt om veel willekeurig gedrag te sturen.
We voegen op dit moment geen scenario toe, dus alles blijft statisch.
Laten we beginnen!
How-to
Een Trumania-circus maak je eenvoudig zo:
from trumania.core import circus
example_circus = circus.Circus(name="example1",
master_seed=12345,
start=pd.Timestamp("1 Jan 2017 00:00"),
step_duration=pd.Timedelta("1h"))
In Trumania worden alle tijdsgebonden elementen bestuurd door een centrale klok. Het belangrijkste in de bovenstaande code is step_duration=pd.Timedelta("1h"), wat bepaalt dat de klok toeneemt in stappen van 1 uur.
Vervolgens voeg je de person-populatie toe aan het circus. Een populatie is in essentie een set agents met een id en wat attributen.
Je kunt de waarden van elk attribuut handmatig specificeren, maar in de meeste gevallen wil je ze willekeurig genereren, dus definiëren we eerst een paar generators daarvoor:
from trumania.core.random_generators import SequencialGenerator, FakerGenerator, NumpyRandomGenerator
id_gen = SequencialGenerator(prefix="PERSON_")
age_gen = NumpyRandomGenerator(method="normal", loc=3, scale=5,
seed=next(example_circus.seeder))
name_gen = FakerGenerator(method="name", seed=next(example_circus.seeder))
Een Trumania-generator is verantwoordelijk voor het leveren van data wanneer de methode generate() wordt aangeroepen. In het voorbeeld hierboven genereert id_gen strings als PERSON_0001, PERSON_0002, ... age_gen zal herhaaldelijk sampelen uit een normale verdeling en name_gen levert willekeurige persoonsnamen.
Merk op dat elke statistische verdeling uit numpy en elke Faker-provider beschikbaar is in Trumania, en dat Trumania eenvoudig is uit te breiden met nieuwe.
Met dit in place kun je een populatie van 1000 personen toevoegen met sequentiële ID's en twee basisattributen op willekeurige basis:
person = example_circus.create_population(name="person", size=1000, ids_gen=id_gen)
person.create_attribute("NAME", init_gen=name_gen)
person.create_attribute("AGE", init_gen=age_gen)
Resultaat
Je kunt de gegenereerde attributen van alle leden van de person-populatie alvast bekijken met person.to_dataframe().
+-------------------+-----------------+---------+
| | NAME | age |
|-------------------+-----------------+---------|
| PERSON_0000000000 | Amy Berger | 28.588 |
| PERSON_0000000001 | Michael Curry | 28.7499 |
| PERSON_0000000002 | Robert Ramirez | 35.9242 |
| PERSON_0000000003 | Derek Gonzalez | 34.7091 |
| PERSON_0000000004 | Gregory Fischer | 25.6009 |
| PERSON_0000000005 | Erica Walker | 33.9758 |
| PERSON_0000000006 | Bradley Collins | 24.4428 |
| PERSON_0000000007 | James Rodriguez | 34.7835 |
| PERSON_0000000008 | Brandy Padilla | 34.6296 |
| PERSON_0000000009 | Mark Taylor | 38.2286 |
+-------------------+-----------------+---------+
Tip: wil je het volledige script zien voor deze basisgebruikerspopulatie, bekijk dan deze snippet op Github!
Hello World-statements: stories maken
Doel
Laten we dit iets interessanter maken door een story toe te voegen. Een Trumania-story is in wezen een stuk scenario dat een dynamisch aspect encapsuleert. Een story wordt elke keer uitgevoerd wanneer de gesimuleerde klok één stap vooruitgaat.
Je hebt hierboven step_duration=pd.Timedelta("1h") ingesteld, dus dit betekent dat elk logisch uur elke story de kans krijgt om te worden uitgevoerd.
Je maakt een eenvoudige hello-world-story waarin alle leden van de person-populatie regelmatig het bericht "hello world" uitsturen.
How-to
Het eerste hoofdonderdeel bij het maken van een story is de populatie die de story initieert. In dit geval is dat person. Het tweede is het definiëren van de timer, die bepaalt wanneer elk lid van de populatie die actie triggert. Voor nu hardcode je dat op 1, wat inhoudt dat alle leden de story bij elke klokstap uitvoeren (je bouwt later uitgebreidere timers):
hello_world = example_circus.create_story(
name="hello_world",
initiating_population=example_circus.populations["person"],
member_id_field="PERSON_ID",
timer_gen=ConstantDependentGenerator(value=1)
)
Tot nu toe is de story leeg en doet hij niets: je moet er operations aan toevoegen. Operations kunnen willekeurig of deterministisch zijn; ze kunnen elk attribuut van een populatie of ander stuk state in het circus lezen en bijwerken, en zelfs side-effects hebben.
Laten we nu twee simpele deterministische operations en een logger toevoegen om daadwerkelijk logs te krijgen:
hello_world.set_operations(
example_circus.clock.ops.timestamp(named_as="TIME"),
ConstantGenerator(value="hello world").ops.generate(named_as="MESSAGE"),
FieldLogger(log_id="hello")
)
De example_circus.clock.ops.timestamp genereert een willekeurige timestamp binnen het huidige tijdsinterval van 1 uur en ConstantGenerator produceert gewoon constante, hardgecodeerde waarden.
Resultaat
Laten we het circus 48 uur aan gesimuleerde tijd laten draaien:
example_circus.run(
duration=pd.Timedelta("48h"),
log_output_folder="example_scenario",
delete_existing_logs=True
)
Dit zou de volgende basisdataset moeten opleveren, waarin elk van de 1000 personen 48 keer "hello world" heeft gezegd over 2 dagen.
Het eerste veld van de resulterende dataset, PERSON_ID, komt overeen met het member_id_field van de story. De andere twee velden, TIME en MESSAGE, komen overeen met de twee hoofdoperations die je in je story hebt gestopt.
+-------+-------------------+---------------------+-------------+
| | PERSON_ID | TIME | MESSAGE |
|-------+-------------------+---------------------+-------------|
| 0 | PERSON_0000000000 | 2017-01-01 01:14:12 | hello world |
| 1 | PERSON_0000000001 | 2017-01-01 01:23:51 | hello world |
| 2 | PERSON_0000000002 | 2017-01-01 01:37:11 | hello world |
| 3 | PERSON_0000000003 | 2017-01-01 01:33:12 | hello world |
| 4 | PERSON_0000000004 | 2017-01-01 01:08:02 | hello world |
| 5 | PERSON_0000000005 | 2017-01-01 01:13:36 | hello world |
| 6 | PERSON_0000000006 | 2017-01-01 01:35:08 | hello world |
| 7 | PERSON_0000000007 | 2017-01-01 01:24:30 | hello world |
| 8 | PERSON_0000000008 | 2017-01-01 01:01:51 | hello world |
| 9 | PERSON_0000000009 | 2017-01-01 01:41:41 | hello world |
| ... |
| 23990 | PERSON_0000000990 | 2017-01-02 23:33:28 | hello world |
| 23991 | PERSON_0000000991 | 2017-01-02 23:15:26 | hello world |
| 23992 | PERSON_0000000992 | 2017-01-02 23:45:56 | hello world |
| 23993 | PERSON_0000000993 | 2017-01-02 23:25:16 | hello world |
| 23994 | PERSON_0000000994 | 2017-01-02 23:33:55 | hello world |
| 23995 | PERSON_0000000995 | 2017-01-02 23:41:45 | hello world |
| 23996 | PERSON_0000000996 | 2017-01-02 23:39:23 | hello world |
| 23997 | PERSON_0000000997 | 2017-01-02 23:45:53 | hello world |
| 23998 | PERSON_0000000998 | 2017-01-02 23:29:25 | hello world |
| 23999 | PERSON_0000000999 | 2017-01-02 23:12:24 | hello world |
+-------+-------------------+---------------------+-------------+
Tip: bekijk de volledige snippet op Github
Iemand om hello world tegen te zeggen
Doel
Laten we dit verbeteren door nog een willekeurig persoon in het gesprek te brengen. Voor nu stel je dat iedere persoon met gelijke kans met iedere andere persoon kan spreken, wat niet heel realistisch is (je voegt later een sociaal netwerk toe). Ook ga je de dataset verrijken door op basis van hun ID de namen van mensen toe te voegen.
How-to
Laten we zeggen dat de andere persoon wordt opgeslagen in een nieuw veld OTHER_PERSON. In dit eenvoudige geval wil je in dat veld gewoon het resultaat plaatsen van een ongewogen willekeurige selectie uit de person-populatie. Je kunt hiervoor simpelweg de willekeurige select_one-operatie van die populatie gebruiken:
example_circus.set_operations(
example_circus.clock.ops.timestamp(named_as="TIME"),
ConstantGenerator(value="hello world").ops.generate(named_as="MESSAGE"),
example_circus.populations["person"].ops.select_one(named_as="OTHER_PERSON"),
example_circus.populations["person"]
.ops.lookup(id_field="PERSON_ID", select={"NAME": "EMITTER_NAME"}),
example_circus.populations["person"]
.ops.lookup(id_field="OTHER_PERSON", select={"NAME": "RECEIVER_NAME"}),
FieldLogger(log_id="hello")
)
Resultaat
Als je het circus opnieuw draait, krijg je een dataset met het nieuwe veld OTHER_PERSON:
+-------+-------------------+---------------------+-------------+-------------------+---------------------+--------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+-------------+-------------------+---------------------+--------------------|
| 0 | PERSON_0000000000 | 2017-01-01 01:14:12 | hello world | PERSON_0000000852 | Ann Cruz | Sophia Black |
| 1 | PERSON_0000000001 | 2017-01-01 01:23:51 | hello world | PERSON_0000000429 | Kimberly Sanchez | Jeffrey Ryan |
| 2 | PERSON_0000000002 | 2017-01-01 01:37:11 | hello world | PERSON_0000000925 | Bethany Smith | Regina Brown |
| 3 | PERSON_0000000003 | 2017-01-01 01:33:12 | hello world | PERSON_0000000347 | Frank Middleton | Jacob Ross |
| 4 | PERSON_0000000004 | 2017-01-01 01:08:02 | hello world | PERSON_0000000211 | Cheryl Decker | Joshua Miller |
| 5 | PERSON_0000000005 | 2017-01-01 01:13:36 | hello world | PERSON_0000000779 | Thomas Rodriguez | Nicole Tanner |
| 6 | PERSON_0000000006 | 2017-01-01 01:35:08 | hello world | PERSON_0000000331 | James Peters | Melissa Rogers |
| 7 | PERSON_0000000007 | 2017-01-01 01:24:30 | hello world | PERSON_0000000234 | Allison Hansen | Taylor Smith |
| 8 | PERSON_0000000008 | 2017-01-01 01:01:51 | hello world | PERSON_0000000678 | Candice Sellers | James Smith |
| 9 | PERSON_0000000009 | 2017-01-01 01:41:41 | hello world | PERSON_0000000108 | Maria White | James Nguyen |
| ... |
| 23990 | PERSON_0000000990 | 2017-01-02 23:33:28 | hello world | PERSON_0000000689 | Natasha Brown | Bailey Ramirez DDS |
| 23991 | PERSON_0000000991 | 2017-01-02 23:15:26 | hello world | PERSON_0000000250 | Shelly Ponce | Jordan Johnson |
| 23992 | PERSON_0000000992 | 2017-01-02 23:45:56 | hello world | PERSON_0000000413 | Steven Mendez | Crystal Duffy |
| 23993 | PERSON_0000000993 | 2017-01-02 23:25:16 | hello world | PERSON_0000000670 | Morgan Rice | Jonathan Obrien |
| 23994 | PERSON_0000000994 | 2017-01-02 23:33:55 | hello world | PERSON_0000000411 | Crystal Vincent | George Mathis |
| 23995 | PERSON_0000000995 | 2017-01-02 23:41:45 | hello world | PERSON_0000000563 | Dawn Kim | Jennifer Martinez |
| 23996 | PERSON_0000000996 | 2017-01-02 23:39:23 | hello world | PERSON_0000000668 | Jimmy Franco | Dr. Mark Ruiz MD |
| 23997 | PERSON_0000000997 | 2017-01-02 23:45:53 | hello world | PERSON_0000000268 | Christian Christian | Victoria Donovan |
| 23998 | PERSON_0000000998 | 2017-01-02 23:29:25 | hello world | PERSON_0000000829 | David Hernandez | Margaret Anderson |
| 23999 | PERSON_0000000999 | 2017-01-02 23:12:24 | hello world | PERSON_0000000944 | Tamara Ramirez | Andrew Curtis |
+-------+-------------------+---------------------+-------------+-------------------+---------------------+--------------------+
Tip: je vindt de volledige code hier
Dat zeg je altijd
Doel
Laten we het verder verbeteren door een willekeurige zin te genereren in het veld MESSAGE in plaats van de saaie, hardgecodeerde string hello world. Hiermee illustreren we een eerste basisgebruik van Trumania's Relationship.
Je gaat elke persoon 4 quotes toewijzen. Telkens wanneer iemand een bericht verstuurt, wordt de inhoud gekozen uit de 4 quotes van die persoon. Bovendien koppel je aan elke quote een ander weight dat bepaalt hoe waarschijnlijk die quote is: een quote met een hoog gewicht zal vaker door de eigenaar worden gebruikt dan een quote met een laag gewicht.
Hoe begin je?
How-to
De eerste stap is een quotegenerator maken, vergelijkbaar met de andere Faker-generators die je hierboven hebt gezien:
quote_generator = FakerGenerator(method="sentence", nb_words=6, variable_nb_words=True,
seed=next(example_circus.seeder))
Je zou die generator net als voorheen rechtstreeks in de story kunnen gebruiken, maar laten we het iets interessanter maken door elke persoon te beperken tot het gebruik van een van zijn of haar 4 favoriete quotes. Dit doe je door een Relationship te creëren tussen de leden van de person-populatie en zinswaarden.
Eerst maak je een lege relationship:
quotes_rel = example_circus.populations["person"].create_relationship("quotes")
Vervolgens vul je deze relationship met 4 quotes voor elke persoon. In de onderstaande code is person.ids een pandas-Series van grootte 1000 met alle id’s van de person-populatie, en quote_generator.generate(size=person.size) levert een andere pandas-Series eveneens van grootte 1000, met willekeurige quotes.
Dit betekent dat elke iteratie van de onderstaande for-loop 1000 relaties toevoegt: één quote voor elk van de 1000 gebruikers. De eerste 1000 quotes krijgen weight=1, de volgende worden frequenter gemaakt met weight=2, ...
for w in range(4):
quotes_rel.add_relations(
from_ids=person.ids,
to_ids=quote_generator.generate(size=person.size),
weights=w
)
Je kunt nu de ConstantGenerator in je story vervangen door een select_one-operatie op die relationship. Dit leest als volgt: voor elke waarde die momenteel in het veld PERSON_ID staat, zoek de relationships in quotes op die van die ID vertrekken en selecteer willekeurig één gerelateerde waarde. Omdat in de onderstaande operatie geen gewichten worden overschreven, worden de standaardgewichten uit add_relations gebruikt:
example_circus.set_operations(
example_circus.clock.ops.timestamp(named_as="TIME"),
example_circus.populations["person"].get_relationship("quotes")
.ops.select_one(from_field="PERSON_ID", named_as="MESSAGE"),
example_circus.populations["person"].ops.select_one(named_as="OTHER_PERSON"),
example_circus.populations["person"]
.ops.lookup(id_field="PERSON_ID", select={"NAME": "EMITTER_NAME"}),
example_circus.populations["person"]
.ops.lookup(id_field="OTHER_PERSON", select={"NAME": "RECEIVER_NAME"}),
FieldLogger(log_id="hello")
)
Resultaat
Als je het circus draait, zou het veld MESSAGE nu de willekeurige quote moeten bevatten:
+-------+-------------------+---------------------+-----------------------------------------------------+-------------------+---------------------+---------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+-----------------------------------------------------+-------------------+---------------------+---------------------|
| 0 | PERSON_0000000000 | 2017-01-01 01:14:12 | Become period risk wait now toward less. | PERSON_0000000158 | Ann Cruz | Victoria Washington |
| 1 | PERSON_0000000001 | 2017-01-01 01:23:51 | Month push down. | PERSON_0000000366 | Kimberly Sanchez | Steven Williams |
| 2 | PERSON_0000000002 | 2017-01-01 01:37:11 | Blue general carry else deep problem area. | PERSON_0000000613 | Bethany Smith | Frances Davis |
| 3 | PERSON_0000000003 | 2017-01-01 01:33:12 | Phone sister pretty suddenly allow conference. | PERSON_0000000086 | Frank Middleton | Ashley Fernandez |
| 4 | PERSON_0000000004 | 2017-01-01 01:08:02 | Each discussion several send wide process. | PERSON_0000000972 | Cheryl Decker | Latoya Flynn |
| 5 | PERSON_0000000005 | 2017-01-01 01:13:36 | Guess can issue writer. | PERSON_0000000030 | Thomas Rodriguez | Lindsay Bailey |
| 6 | PERSON_0000000006 | 2017-01-01 01:35:08 | Boy step claim camera common but our. | PERSON_0000000548 | James Peters | Thomas Ward |
| ... | 23993 | PERSON_0000000993 | 2017-01-02 23:25:16 | May course and kill from news. | PERSON_0000000202 | Morgan Rice | Patricia Williams |
| 23994 | PERSON_0000000994 | 2017-01-02 23:33:55 | Water door live hospital together safe. | PERSON_0000000268 | Crystal Vincent | Victoria Donovan |
| 23995 | PERSON_0000000995 | 2017-01-02 23:41:45 | Stage control management read although thousand. | PERSON_0000000466 | Dawn Kim | Ashley Baxter |
| 23996 | PERSON_0000000996 | 2017-01-02 23:39:23 | Their that region majority break article. | PERSON_0000000620 | Jimmy Franco | Alex Dominguez |
| 23997 | PERSON_0000000997 | 2017-01-02 23:45:53 | Lead simple audience response eye. | PERSON_0000000319 | Christian Christian | David Smith |
| 23998 | PERSON_0000000998 | 2017-01-02 23:29:25 | Vote man son yeah child. | PERSON_0000000487 | David Hernandez | Joshua Park |
| 23999 | PERSON_0000000999 | 2017-01-02 23:12:24 | Answer region wind condition someone bed cover. | PERSON_0000000237 | Tamara Ramirez | Beverly Rodriguez |
+-------+-------------------+---------------------+-----------------------------------------------------+-------------------+---------------------+---------------------+
Het punt van de relationship voor de quotes is dat de velden van de gegenereerde dataset nu gerelateerd zijn: sommige zinnen worden vaker gebruikt door bepaalde gebruikers.
Tip: bekijk de volledige code op Github
Het gaat niet om wat je doet, maar wanneer je het doet
Doel
Tot nu toe produceert elk lid van de person-populatie elk uur van de dag precies één bericht. Laten we dat wat realistischer maken.
Trumania gebruikt een combinatie van 2 concepten om het tijdsaspect van de uitvoering van een story te sturen: activiteitsniveaus en timerprofielen. Samen bepalen ze de kans om de story te triggeren, gegeven een specifieke gebruiker en het tijdstip van de dag.
How-to
Je werkt de story bij en parametriseert deze twee aspecten.
Maak eerst het timerprofiel van de story, dat bepaalt wanneer er meer of minder acties worden getriggerd. Dit profiel is hetzelfde voor alle leden van de populatie die de story triggeren.
Trumania wordt geleverd met een paar interessante standaardprofielen, zoals DefaultDailyTimerGenerator, dat overeenkomt met het dagelijkse telefoongebruik dat we in een echte productiedataset hebben gemeten:

Laten we er een instantieren in je scenario:
from trumania.components.time_patterns.profilers import DefaultDailyTimerGenerator
story_timer_gen = DefaultDailyTimerGenerator(
clock=example_circus.clock,
seed=next(example_circus.seeder))
Ten tweede zijn er de activiteitsniveaus van de populatieleden voor die story, die bepalen hoe actief elke gebruiker is binnen die story. Je hebt de vrijheid om voor elk lid een ander niveau in te stellen, zodat sommigen die actie vaker of minder vaak triggeren dan anderen.
Je zou elk niveau handmatig kunnen specificeren, maar hier gebruik je gewoon een willekeurige generator die een activiteitsniveau aan elk populatielid toekent.
Eerst definiëren we drie activiteitsniveaus van gemiddeld 3, 10 en 20 triggers per dag:
low_activity = story_timer_gen.activity(n=3, per=pd.Timedelta("1 day"))
med_activity = story_timer_gen.activity(n=10, per=pd.Timedelta("1 day"))
high_activity = story_timer_gen.activity(n=20, per=pd.Timedelta("1 day"))
Vervolgens maak je de feitelijke activity generator, opnieuw een Numpy-generator, ditmaal gebaseerd op de choice-methode. De onderstaande kent 20% van de populatie een lage activiteit toe, 70% een middelmatige en 10% een hoge:
activity_gen = NumpyRandomGenerator(
method="choice",
a=[low_activity, med_activity, high_activity],
p=[.2, .7, .1],
seed=next(example_circus.seeder))
En nu kun je dit timerprofiel en deze activity generator gebruiken als onderdeel van je story:
hello_world = example_circus.create_story(
name="hello_world",
initiating_population=example_circus.populations["person"],
member_id_field="PERSON_ID",
timer_gen=story_timer_gen,
activity_gen=activity_gen
)
Resultaat
De resulterende dataset heeft dezelfde schema als voorheen:
+-------+-------------------+---------------------+---------------------------------------------------------+-------------------+---------------------+----------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+---------------------------------------------------------+-------------------+---------------------+----------------------|
| 0 | PERSON_0000000016 | 2017-01-01 00:14:12 | Still try sex sure. | PERSON_0000000739 | Johnny Moore | Adam Barrett |
| 1 | PERSON_0000000063 | 2017-01-01 00:23:51 | Resource magazine wide. | PERSON_0000000511 | Cody Pham | Tina Simmons |
| 2 | PERSON_0000000064 | 2017-01-01 00:37:11 | Heat sure simple letter better forget. | PERSON_0000000044 | Katherine Fleming | Angela Gray |
| 3 | PERSON_0000000076 | 2017-01-01 00:33:12 | Star our conference always place ball. | PERSON_0000000959 | Benjamin Reese II | Barbara Alexander |
| 4 | PERSON_0000000088 | 2017-01-01 00:08:02 | Hot event five left become time commercial. | PERSON_0000000000 | Zachary Cook | Ann Cruz |
| ... |
| 28556 | PERSON_0000000962 | 2017-01-04 00:07:45 | Then include rock rule scientist condition. | PERSON_0000000448 | Johnny Washington | Melissa Adams |
| 28557 | PERSON_0000000980 | 2017-01-04 00:04:44 | Collection full argue interview property pattern never. | PERSON_0000000731 | James Ramirez | David Morrow |
| 28558 | PERSON_0000000983 | 2017-01-04 00:23:21 | High day prepare see. | PERSON_0000000509 | Evan Horne | Robert Sims |
| 28559 | PERSON_0000000988 | 2017-01-04 00:43:56 | Likely south school result case federal seat. | PERSON_0000000819 | Benjamin Campbell | Michelle Jackson DDS |
+-------+-------------------+---------------------+---------------------------------------------------------+-------------------+---------------------+----------------------+
Tip: bekijk de snippet op Github
Resultaatanalyse
De tijdspatronen die je in de configuratie van je scenario hebt opgenomen, zouden nu zichtbaar moeten zijn in de interne structuur van de resulterende dataset. Je kunt dat aantonen met een basale data-analyse op de resulting_dataset:
- het aantal berichten per uur van de dag zou de opgegeven verdeling moeten volgen (let op hoe vergelijkbaar het is met de curve van
DefaultDailyTimerGeneratorhierboven):
time_profile = (
resulting_dataset[["MESSAGE", "TIME"]]
.groupby(by=example_5_df.TIME.dt.hour)["MESSAGE"]
.count()
)
time_profile.plot()

- Het histogram van het aantal berichten per gebruiker zou ook je configuratie moeten respecteren:
- je hebt
low_activityingesteld op gemiddeld 3 berichten per dag,med_activityop 10 enhigh_activityop 20, en je draaide de simulatie over 5 dagen. Dit zou 3 groepenpersonmoeten opleveren met grofweg 15, 50 en 100 totale berichten, overeenkomend met de 3 pieken in het onderstaande histogram - je stelde de kansen voor deze activiteitsniveaus in op .2, .7 en .1, en dit komt opnieuw grofweg overeen met respectievelijk de oppervlakte onder elke "bult" van het histogram:
- je hebt
usage_per_user = resulting_dataset[["MESSAGE", "PERSON_ID"]].groupby("PERSON_ID")["MESSAGE"].count()
usage_per_user.plot(kind="hist")

Het sociale netwerk
Doel
Je kunt het scenario nog verder verbeteren door mensen met elkaar te verbinden, zodat ze tijdens de uitvoering van je story berichten naar hun vrienden sturen in plaats van naar een willekeurig persoon. Dit introduceert veel structuur in de resulterende dataset, omdat de sociale graaf nu uit het berichtenlog afleidbaar zou moeten zijn.
How-to
Je kunt het sociale netwerk opnieuw modelleren met een Relationship, hetzelfde concept dat je hierboven gebruikte voor quotes. Je herinnert je misschien dat de quotes-relationship een gewicht had. Dat gewicht bepaalde welke quote meer of minder waarschijnlijk was, gegeven een persoon. Hier kun je hetzelfde doen en gewichten gebruiken om te bepalen welke vrienden vaker of minder vaak worden gebeld dan anderen.
Merk op dat je hier een relationship definieert van person naar person, hoewel Trumania in het algemeen relaties tussen willekeurige paren populaties mogelijk maakt.
Er zijn veel manieren om de randen van een sociale graaf te genereren. Eén simpele en klassieke manier is leunen op het Erdos-Renyi-algoritme. Dit is bijzonder eenvoudig in Trumania, omdat het al in het framework zit. Je hoeft het alleen in je scenario te injecteren om een relationship te maken.
Hier definieer je een sociale graaf tussen de leden van de person-relationship waarin elke persoon 20 vrienden heeft. Merk op dat er automatisch een gewicht wordt toegevoegd aan die relationship met een Paretoverdeling.
from trumania.components.social_networks.erdos_renyi import WithErdosRenyi
# self is het circus hier; zie de voorbeeldcode op github voor details
self.add_er_social_network_relationship(
self.populations["person"],
relationship_name="friends",
average_degree=20)
Met deze relationship op zijn plek kun je onze story opnieuw bekijken. Dit keer wordt OTHER_PERSON niet langer willekeurig uit de populatie gekozen, maar geselecteerd uit de vrienden van elk person-lid, met een kans evenredig aan het gewicht gedefinieerd in het sociale netwerk:
hello_world.set_operations(
self.clock.ops.timestamp(named_as="TIME"),
self.populations["person"].get_relationship("quotes")
.ops.select_one(from_field="PERSON_ID",named_as="MESSAGE"),
self.populations["person"]
.get_relationship("friends")
.ops.select_one(from_field="PERSON_ID", named_as="OTHER_PERSON"),
self.populations["person"]
.ops.lookup(id_field="PERSON_ID", select={"NAME": "EMITTER_NAME"}),
self.populations["person"]
.ops.lookup(id_field="OTHER_PERSON", select={"NAME": "RECEIVER_NAME"}),
operations.FieldLogger(log_id="hello_6")
)
Resultaat
Opnieuw heeft de resulterende dataset dezelfde dataschema als voorheen, maar dit keer met nog meer structuur dankzij je basis sociale netwerk:
+-------+-------------------+---------------------+----------------------------------------------------+-------------------+-------------------+------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+----------------------------------------------------+-------------------+-------------------+------------------|
| 0 | PERSON_0000000004 | 2017-01-01 00:14:12 | Clearly see sure. | PERSON_0000000321 | Cheryl Decker | Ian White |
| 1 | PERSON_0000000012 | 2017-01-01 00:23:51 | Offer or interview clear structure watch capital. | PERSON_0000000697 | Kelly Green | Ashley Turner |
| 2 | PERSON_0000000018 | 2017-01-01 00:37:11 | Recently race draw thousand around ahead. | PERSON_0000000989 | Laura Stephenson | Hannah James |
| 3 | PERSON_0000000040 | 2017-01-01 00:33:12 | To protect man image power beyond. | PERSON_0000000418 | Jeffrey Miller | Scott Collins |
... |
| 28966 | PERSON_0000000985 | 2017-01-04 00:09:23 | However agreement fear door land hotel. | PERSON_0000000211 | Laura Mason | Joshua Miller |
| 28967 | PERSON_0000000995 | 2017-01-04 00:48:08 | Top heat window quite forward friend somebody. | PERSON_0000000171 | Dawn Kim | Sandra Phillips |
| 28968 | PERSON_0000000999 | 2017-01-04 00:58:46 | Answer region wind condition someone bed cover. | PERSON_0000000252 | Tamara Ramirez | Jordan Collins |
+-------+-------------------+---------------------+----------------------------------------------------+-------------------+-------------------+------------------+
Tip: bekijk de snippet op Github
Resultaatanalyse
Een heel ruwe manier om naar dat sociale netwerk te kijken is simpelweg het aantal unieke vrienden te tellen dat door elke persoon is gebeld. Als je de simulatie lang genoeg draait, zou dit je een ondergrens moeten geven van het werkelijke aantal mensen in iemands sociale netwerk (omdat je gewichten met een Paretoverdeling gebruikt, hebben veel mensen vrienden die echt onwaarschijnlijk gebeld worden, dus moet de simulatie heel lang lopen om elke mogelijke gebelde persoon echt te observeren).
Je voert ons scenario 15 gesimuleerde dagen uit en krijgt dit, dat zeer dicht bij en iets onder de gemiddelde graad van 20 ligt die je in je sociale graaf hebt ingesteld:
social_group_size = (
resulting_dataset[["PERSON_ID", "OTHER_PERSON"]]
.groupby("PERSON_ID")
.agg(lambda friends_ids: len(friends_ids.unique()))
)
social_group_size.plot(kind="hist")

Er is meer!
Verschillende features van Trumania zijn niet beschreven in dit bericht:
- meerdere populaties in één scenario
- meerdere stories in één scenario
- probabilistische afhankelijkheden tussen stories toevoegen (bijv. iemand belt een vriend nadat hij gebeld is)
- state-updates (bijv. saldo verlagen op basis van gespreksduur)
- herbruikbare stukken scenario, zoals geografieën, meer timerprofielen, ...
- persistentie van stukken scenario
Conclusie
We hebben Trumania geïntroduceerd als een scenario-gebaseerde datageneratorbibliotheek in Python. De gegenereerde datasets kunnen worden gebruikt voor uiteenlopende toepassingen zoals testen, leren en benchmarken.
We legden uit dat we voor het goed testen van een applicatie of algoritme datasets nodig hebben die bepaalde verwachte statistische eigenschappen respecteren. We illustreerden dat Trumania dat kan in een voorbeeld waarin we een basis-"berichtenlog"-dataset genereerden die een verdeling van het aantal berichten per gebruiker respecteert, een verdeling over de tijd van de dag en een verdeling van oproepen binnen een sociale graaf.
We hopen ook te hebben laten zien dat de flexibiliteit van Trumania-componenten het mogelijk maakt om een breed scala aan scenario’s te creëren.
Een Trumania-scenario kan relaties tussen entiteiten bevatten en kan worden geconfigureerd met theoretische willekeurige verdelingen, empirische verdelingen waargenomen in een echte dataset of zelfs echte productiedata (bijv. een dataset met locatie-ID’s). We toonden dit door een statistische verdeling van telefoongebruikstimestamps, waargenomen in een echte dataset, te hergebruiken voor de configuratie van de willekeurige timer van onze story.
Ga hiermee los! Ga naar Trumania's Github en schrijf je eigen scenario ;)
Wil je anders contact opnemen met Svend Vanderveken of coauteur Milan van der Meer:
- Svend Vanderveken is freelance software engineer met expertise in dataverwerkingsoplossingen, vooral in Kafka, Scala, SQL, python... Volg hem via zijn blog svend.kelesia.com en op Twitter @sv3ndk.
- Milan van der Meer werkt momenteel als software engineer bij Real Impact Analytics. Je kunt altijd met hem connecten op LinkedIn
Redactionele noot: deze post is geschreven in samenwerking met Milan van der Meer. Beide auteurs van deze post maken deel uit van het team van Real Impact Analytics, een innovatieve Belgische big data-startup die de waarde in telecomdata ontsluit door "big data te appificeren".
Deze tutorial geeft je een voorproefje van waarom je willekeurige datasets zou willen genereren en wat je ervan kunt verwachten. Ook loop je door de eerste voorbeelden heen van hoe je Trumania gebruikt, een Python-bibliotheek voor datageneratie.
Voor meer informatie kun je terecht op Trumania's GitHub!
Waarom willekeurige datasets genereren?
Het genereren van willekeurige datasets is relevant voor zowel data engineers als data scientists. Waarom werken data scientists en data engineers met synthetische data en hoe verkrijgen ze die?
Als data engineer, nadat je je nieuwe geweldige dataprocestoepassing hebt geschreven, denk je dat het tijd is om end-to-end te testen en heb je dus invoergegevens nodig.
Als data scientist kun je profiteren van datageneratie, omdat je dan kunt experimenteren met verschillende manieren om datasets te verkennen, algoritmes, datavisualisatietechnieken of aannames over het gedrag van een methode valideren tegen veel verschillende datasets naar keuze.
In beide gevallen is het verleidelijk om gewoon echte data te gebruiken. Eén klein probleem: productiedata is meestal moeilijk te verkrijgen, zelfs gedeeltelijk, en met nieuwe Europese wetten over privacy en beveiliging wordt dat er niet makkelijker op.
Misschien ben je een van de gelukkigen en heb je een mooie, complete productiedataset op je laptop. Zelfs dan wil je meer variatie in de dataset om te valideren tegen verschillende situaties, zoals uiteenlopende populatiegroottes, verschillende dataverdelingen, diverse datakwaliteitsproblemen zoals ontbrekende of ongeldige data...
Voor een uitgebreidere uitleg over waarom het genereren van willekeurige datasets nuttig is, ga naar 'Why synthetic data is about to become a major competitive advantage'.
Schema-gebaseerde willekeurige datageneratie: we hebben goede relaties nodig!
Dit onderdeel probeert schema-gebaseerde willekeurige datageneratie te illustreren en de beperkingen ervan te tonen.
Er bestaan al veel tools om willekeurige datasets te genereren. Een veelgebruikte aanpak is schema-gebaseerde generatie, waarbij je een blauwdruk definieert en die gebruikt om entiteiten te genereren. Khermes en LogSynth zijn twee voorbeelden van zulke tools.
Een voorbeeld van een schema-gebaseerde config zou misschien dit persoonschema bevatten:
{
{
"field": "Name",
"class": NameGenerator
},
{
"field": "Age",
"class": RandomInt(18, 65)
},
{
"field": "Gender",
"class": RandomPicker(["Male", "Female", "Other"])
}
}
Dit schema definieert het genereren van wat gegevens over een persoon. Het bevat een naam, leeftijd en geslacht. Het is eenvoudig, het is snel, en het heeft wat beperkingen. Een belangrijke beperking is het fingeren van relaties tussen verschillende entiteiten of op basis van tijd.
Hoe maak je bijvoorbeeld sommige namen waarschijnlijker op basis van geslacht? Of hoe maak je een log van gebruikersacties waarin acties vaker plaatsvinden in het weekend?
De schema-gebaseerde aanpak maakt het niet makkelijk om je nieuwe toepassing bloot te stellen aan echte specificiteiten of moeilijkheden zoals:
- Onevenwichtige datasets (moeilijkheden bij group-by-achtige bewerkingen);
- Geclusterde patronen in het entiteitennetwerk. Dus sociale groepen ontdekken via gebruikersinteracties (bijvoorbeeld telefoongesprekken);
- Verschillende tijdsactiviteitsprofielen. Acties vinden vaker plaats op bepaalde tijdstippen van de dag of week;
- Causale relaties tussen acties. Dus veel aankoopacties in een winkel die uiteindelijk een "out-of-stock"-event triggeren.
Trumania is gebaseerd op scenario's om deze tekortkomingen aan te pakken en realistischer datasets te genereren. Terwijl het scenario zich ontvouwt, interacteren verschillende populaties met elkaar, werken hun eigenschappen bij en zenden logs uit.
Een voorbeeld van een scenario zou zijn dat mensen hun vrienden bellen. Wanneer iemand belt, betalen ze in minuten; als de minuten op zijn, moeten ze naar een winkel... De resulterende dataset kan de log zijn van gebruikersgesprekken, de log van winkelacties en het voorraadniveau per uur. Acties in een scenario kunnen willekeurig of deterministisch zijn.
In Trumania zijn de gegenereerde datasets meestal tijdreeksen, omdat ze voortkomen uit de uitvoering van een scenario dat zich in de tijd afspeelt.
Onderling gerelateerde data genereren met Trumania (aka Tutorial Time!)
In het onderstaande voorbeeld zie je hoe je een basissceanrio uitwerkt waarin verschillende mensen elkaar berichten sturen. De resulterende dataset wordt de tijdreeks van berichten die tussen mensen zijn uitgewisseld.
De stappen die we zullen doorlopen zijn als volgt:
- een Trumania-circus maken, de wereld waarin het scenario wordt uitgevoerd
- een "hello world"-story toevoegen die basis hardcoded logs produceert
- een relatie toevoegen om enkele favoriete quotes aan elke persoon toe te wijzen, die worden gebruikt om de inhoud van de berichten te maken.
- de tijdfactoren van de story parametriseren om een realistischer verdeling van berichten over de dag te krijgen
- nog een relatie toevoegen om een sociaal netwerk te definiëren, zodat de sociale graaf die uit de berichtenlog ontstaat realistischer is
Een Trumania-circus maken
Doel
De eerste stap is het maken van een circus, de wereld waarin alle elementen van het scenario zullen bestaan. We gaan ook een basispopulatie van persons maken. Omdat veel aspecten van Trumania willekeurig zijn, introduceert deze stap ook het concept generators, die worden gebruikt om veel willekeurig gedrag te sturen.
We voegen op dit moment nog geen scenario toe, dus alles blijft statisch.
Laten we beginnen!
How-to
Een Trumania-circus maak je eenvoudig als volgt:
from trumania.core import circus
example_circus = circus.Circus(name="example1",
master_seed=12345,
start=pd.Timestamp("1 Jan 2017 00:00"),
step_duration=pd.Timedelta("1h"))
In Trumania worden alle tijdsgerelateerde elementen aangestuurd door één centrale klok. Het belangrijkste deel in de code hierboven is step_duration=pd.Timedelta("1h"), wat bepaalt dat de klok met stappen van 1 uur wordt verhoogd.
Vervolgens voeg je de person-populatie toe aan het circus. Een populatie is in essentie een set agenten met een id en enkele attributen.
Het is mogelijk om de waarden van elk attribuut handmatig te specificeren, maar in de meeste gevallen wil je ze willekeurig genereren. Laten we daarvoor eerst een paar generators definiëren:
from trumania.core.random_generators import SequencialGenerator, FakerGenerator, NumpyRandomGenerator
id_gen = SequencialGenerator(prefix="PERSON_")
age_gen = NumpyRandomGenerator(method="normal", loc=3, scale=5,
seed=next(example_circus.seeder))
name_gen = FakerGenerator(method="name", seed=next(example_circus.seeder))
Een Trumania-generator is verantwoordelijk voor het aanleveren van data wanneer de methode generate() wordt aangeroepen. In het voorbeeld hierboven genereert id_gen strings zoals PERSON_0001, PERSON_0002, ... age_gen zal herhaaldelijk data samplen uit een normale verdeling en name_gen levert willekeurige persoonsnamen.
Merk op dat elke statistische verdeling uit numpy evenals elke Faker-provider beschikbaar is in Trumania, en dat Trumania eenvoudig kan worden uitgebreid met nieuwe.
Met dit alles op zijn plek kun je een populatie van 1000 personen toevoegen met sequentiële ID's en twee basis willekeurige attributen:
person = example_circus.create_population(name="person", size=1000, ids_gen=id_gen)
person.create_attribute("NAME", init_gen=name_gen)
person.create_attribute("AGE", init_gen=age_gen)
Resultaat
Je kunt de gegenereerde attributen van alle leden van de person-populatie al bekijken door person.to_dataframe() aan te roepen.
+-------------------+-----------------+---------+
| | NAME | age |
|-------------------+-----------------+---------|
| PERSON_0000000000 | Amy Berger | 28.588 |
| PERSON_0000000001 | Michael Curry | 28.7499 |
| PERSON_0000000002 | Robert Ramirez | 35.9242 |
| PERSON_0000000003 | Derek Gonzalez | 34.7091 |
| PERSON_0000000004 | Gregory Fischer | 25.6009 |
| PERSON_0000000005 | Erica Walker | 33.9758 |
| PERSON_0000000006 | Bradley Collins | 24.4428 |
| PERSON_0000000007 | James Rodriguez | 34.7835 |
| PERSON_0000000008 | Brandy Padilla | 34.6296 |
| PERSON_0000000009 | Mark Taylor | 38.2286 |
+-------------------+-----------------+---------+
Tip: als je het volledige script voor deze basisgebruikerspopulatie wilt zien, bekijk dan deze snippet op Github!
Hello World-statements: stories maken
Doel
We maken het iets interessanter door een story toe te voegen. Een Trumania-story is in wezen een stuk scenario dat een dynamisch aspect encapsuleert. Een story wordt elke keer uitgevoerd wanneer de gesimuleerde klok één stap vooruit gaat.
Onthoud dat je step_duration=pd.Timedelta("1h") hebt ingesteld in het circus hierboven, dus dit impliceert dat elk logisch uur elke story een kans krijgt om uitgevoerd te worden.
Je gaat een eenvoudige hello-world-story maken waarin alle leden van de bovenstaande person-populatie regelmatig het bericht "hello world" uitsturen.
How-to
Het eerste belangrijke aspect bij het maken van een story is de populatie die de story initieert. In dit geval is dat de person. Het tweede aspect is het definiëren van de timer, die aangeeft wanneer elk lid van de populatie die actie triggert. Voor nu hardcode je die op 1, wat inhoudt dat alle leden de story bij elke klokstap uitvoeren (later bouw je uitgebreidere timers):
hello_world = example_circus.create_story(
name="hello_world",
initiating_population=example_circus.populations["person"],
member_id_field="PERSON_ID",
timer_gen=ConstantDependentGenerator(value=1)
)
Tot nu toe is de story leeg en doet die niets: je moet er enkele operations aan toevoegen. Operaties kunnen willekeurig of deterministisch zijn, ze kunnen elk attribuut van een populatie of ander stuk state in het circus lezen en bijwerken en zelfs bijwerkingen hebben.
Voor nu voegen we twee eenvoudige deterministische operaties toe en een logger om daadwerkelijk logs te krijgen:
hello_world.set_operations(
example_circus.clock.ops.timestamp(named_as="TIME"),
ConstantGenerator(value="hello world").ops.generate(named_as="MESSAGE"),
FieldLogger(log_id="hello")
)
De example_circus.clock.ops.timestamp genereert een willekeurige timestamp binnen het huidige tijdsinterval van 1 uur en ConstantGenerator produceert gewoon constante hardcoded waarden.
Resultaat
Laten we het circus 48 uur gesimuleerde tijd laten draaien:
example_circus.run(
duration=pd.Timedelta("48h"),
log_output_folder="example_scenario",
delete_existing_logs=True
)
Dit zou de volgende basisdataset moeten opleveren, waarin elk van de 1000 personen 48 keer "hello world" zei over 2 dagen.
Het eerste veld van de resulterende dataset, PERSON_ID, komt overeen met het member_id_field van de story. De andere twee velden, TIME en MESSAGE, komen overeen met de twee hoofdoperaties die je in je story hebt gezet.
+-------+-------------------+---------------------+-------------+
| | PERSON_ID | TIME | MESSAGE |
|-------+-------------------+---------------------+-------------|
| 0 | PERSON_0000000000 | 2017-01-01 01:14:12 | hello world |
| 1 | PERSON_0000000001 | 2017-01-01 01:23:51 | hello world |
| 2 | PERSON_0000000002 | 2017-01-01 01:37:11 | hello world |
| 3 | PERSON_0000000003 | 2017-01-01 01:33:12 | hello world |
| 4 | PERSON_0000000004 | 2017-01-01 01:08:02 | hello world |
| 5 | PERSON_0000000005 | 2017-01-01 01:13:36 | hello world |
| 6 | PERSON_0000000006 | 2017-01-01 01:35:08 | hello world |
| 7 | PERSON_0000000007 | 2017-01-01 01:24:30 | hello world |
| 8 | PERSON_0000000008 | 2017-01-01 01:01:51 | hello world |
| 9 | PERSON_0000000009 | 2017-01-01 01:41:41 | hello world |
| ... |
| 23990 | PERSON_0000000990 | 2017-01-02 23:33:28 | hello world |
| 23991 | PERSON_0000000991 | 2017-01-02 23:15:26 | hello world |
| 23992 | PERSON_0000000992 | 2017-01-02 23:45:56 | hello world |
| 23993 | PERSON_0000000993 | 2017-01-02 23:25:16 | hello world |
| 23994 | PERSON_0000000994 | 2017-01-02 23:33:55 | hello world |
| 23995 | PERSON_0000000995 | 2017-01-02 23:41:45 | hello world |
| 23996 | PERSON_0000000996 | 2017-01-02 23:39:23 | hello world |
| 23997 | PERSON_0000000997 | 2017-01-02 23:45:53 | hello world |
| 23998 | PERSON_0000000998 | 2017-01-02 23:29:25 | hello world |
| 23999 | PERSON_0000000999 | 2017-01-02 23:12:24 | hello world |
+-------+-------------------+---------------------+-------------+
Tip: bekijk de volledige snippet op Github
Iemand om Hello World tegen te zeggen
Doel
Laten we dit verbeteren door nog een willekeurige persoon aan het gesprek toe te voegen. Voor nu zeg je dat elke persoon met gelijke waarschijnlijkheid met elke andere persoon kan praten, wat niet erg realistisch is (later voeg je een sociaal netwerk toe). Ook ga je de dataset verrijken door de namen van mensen toe te voegen op basis van hun ID.
How-to
Laten we zeggen dat de andere persoon wordt opgeslagen in een nieuw veld OTHER_PERSON. In dit simplistische geval wil je in dat veld gewoon het resultaat zetten van een willekeurige, ongewogen selectie uit de person-populatie. Je kunt simpelweg de willekeurige operatie select_one van die populatie gebruiken:
example_circus.set_operations(
example_circus.clock.ops.timestamp(named_as="TIME"),
ConstantGenerator(value="hello world").ops.generate(named_as="MESSAGE"),
example_circus.populations["person"].ops.select_one(named_as="OTHER_PERSON"),
example_circus.populations["person"]
.ops.lookup(id_field="PERSON_ID", select={"NAME": "EMITTER_NAME"}),
example_circus.populations["person"]
.ops.lookup(id_field="OTHER_PERSON", select={"NAME": "RECEIVER_NAME"}),
FieldLogger(log_id="hello")
)
Resultaat
Wanneer je het circus opnieuw draait, krijg je een dataset met het nieuwe veld OTHER_PERSON:
+-------+-------------------+---------------------+-------------+-------------------+---------------------+--------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+-------------+-------------------+---------------------+--------------------|
| 0 | PERSON_0000000000 | 2017-01-01 01:14:12 | hello world | PERSON_0000000852 | Ann Cruz | Sophia Black |
| 1 | PERSON_0000000001 | 2017-01-01 01:23:51 | hello world | PERSON_0000000429 | Kimberly Sanchez | Jeffrey Ryan |
| 2 | PERSON_0000000002 | 2017-01-01 01:37:11 | hello world | PERSON_0000000925 | Bethany Smith | Regina Brown |
| 3 | PERSON_0000000003 | 2017-01-01 01:33:12 | hello world | PERSON_0000000347 | Frank Middleton | Jacob Ross |
| 4 | PERSON_0000000004 | 2017-01-01 01:08:02 | hello world | PERSON_0000000211 | Cheryl Decker | Joshua Miller |
| 5 | PERSON_0000000005 | 2017-01-01 01:13:36 | hello world | PERSON_0000000779 | Thomas Rodriguez | Nicole Tanner |
| 6 | PERSON_0000000006 | 2017-01-01 01:35:08 | hello world | PERSON_0000000331 | James Peters | Melissa Rogers |
| 7 | PERSON_0000000007 | 2017-01-01 01:24:30 | hello world | PERSON_0000000234 | Allison Hansen | Taylor Smith |
| 8 | PERSON_0000000008 | 2017-01-01 01:01:51 | hello world | PERSON_0000000678 | Candice Sellers | James Smith |
| 9 | PERSON_0000000009 | 2017-01-01 01:41:41 | hello world | PERSON_0000000108 | Maria White | James Nguyen |
| ... |
| 23990 | PERSON_0000000990 | 2017-01-02 23:33:28 | hello world | PERSON_0000000689 | Natasha Brown | Bailey Ramirez DDS |
| 23991 | PERSON_0000000991 | 2017-01-02 23:15:26 | hello world | PERSON_0000000250 | Shelly Ponce | Jordan Johnson |
| 23992 | PERSON_0000000992 | 2017-01-02 23:45:56 | hello world | PERSON_0000000413 | Steven Mendez | Crystal Duffy |
| 23993 | PERSON_0000000993 | 2017-01-02 23:25:16 | hello world | PERSON_0000000670 | Morgan Rice | Jonathan Obrien |
| 23994 | PERSON_0000000994 | 2017-01-02 23:33:55 | hello world | PERSON_0000000411 | Crystal Vincent | George Mathis |
| 23995 | PERSON_0000000995 | 2017-01-02 23:41:45 | hello world | PERSON_0000000563 | Dawn Kim | Jennifer Martinez |
| 23996 | PERSON_0000000996 | 2017-01-02 23:39:23 | hello world | PERSON_0000000668 | Jimmy Franco | Dr. Mark Ruiz MD |
| 23997 | PERSON_0000000997 | 2017-01-02 23:45:53 | hello world | PERSON_0000000268 | Christian Christian | Victoria Donovan |
| 23998 | PERSON_0000000998 | 2017-01-02 23:29:25 | hello world | PERSON_0000000829 | David Hernandez | Margaret Anderson |
| 23999 | PERSON_0000000999 | 2017-01-02 23:12:24 | hello world | PERSON_0000000944 | Tamara Ramirez | Andrew Curtis |
+-------+-------------------+---------------------+-------------+-------------------+---------------------+--------------------+
Tip: je kunt de volledige code hier bekijken
Dat zeg je altijd
Doel
Laten we verder verbeteren door een willekeurige zin te genereren in het veld MESSAGE in plaats van een saaie hardcoded string hello world. Zo kun je een eerste basisgebruik van Trumania's Relationship illustreren.
Je gaat elke persoon koppelen aan 4 quotes. Telkens wanneer iemand een bericht verstuurt, wordt de inhoud gekozen uit de 4 quotes van die persoon. Bovendien koppel je aan elke quote een ander weight dat bepaalt hoe waarschijnlijk die quote is: een quote met een hoog gewicht zal vaker door de eigenaar gebruikt worden dan een quote met een laag gewicht.
Hoe begin je hieraan?
How-to
De eerste stap is een quotegenerator maken, vergelijkbaar met de andere Faker-generatoren die je hierboven zag:
quote_generator = FakerGenerator(method="sentence", nb_words=6, variable_nb_words=True,
seed=next(example_circus.seeder))
Je zou die generator als zodanig in de story kunnen gebruiken, maar laten we het iets interessanter maken door elke persoon te beperken tot het gebruik van een van z'n 4 favoriete quotes. Dat doe je door een Relationship te maken tussen de leden van de person-populatie en enkele zinswaarden.
Eerst maak je een lege relatie:
quotes_rel = example_circus.populations["person"].create_relationship("quotes")
Vervolgens vul je deze relatie met 4 quotes voor elke persoon. In de onderstaande code is person.ids een pandas-Series van grootte 1000 met alle id's van de person-populatie, en quote_generator.generate(size=person.size) levert een andere pandas-Series ook van grootte 1000, met willekeurige quotes.
Dit betekent dat elke doorgang van de for-lus hieronder 1000 relaties in de relationship vult: één quote toevoegen aan elk van de 1000 gebruikers. De eerste 1000 quotes worden gekoppeld aan weight=1, de volgende worden frequenter gemaakt door ze te koppelen aan weight=2, ...
for w in range(4):
quotes_rel.add_relations(
from_ids=person.ids,
to_ids=quote_generator.generate(size=person.size),
weights=w
)
Je kunt nu de ConstantGenerator in je story vervangen door een select_one-operatie op die relatie. Dit leest als volgt: voor elke waarde die momenteel in het veld PERSON_ID staat, zoek de relaties in quotes op die van die ID vertrekken en selecteer willekeurig één gerelateerde waarde. Omdat in de onderstaande operatie geen gewichten worden overschreven, worden de standaardrelatiegewichten gebruikt die hierboven in add_relations zijn gedefinieerd:
example_circus.set_operations(
example_circus.clock.ops.timestamp(named_as="TIME"),
example_circus.populations["person"].get_relationship("quotes")
.ops.select_one(from_field="PERSON_ID", named_as="MESSAGE"),
example_circus.populations["person"].ops.select_one(named_as="OTHER_PERSON"),
example_circus.populations["person"]
.ops.lookup(id_field="PERSON_ID", select={"NAME": "EMITTER_NAME"}),
example_circus.populations["person"]
.ops.lookup(id_field="OTHER_PERSON", select={"NAME": "RECEIVER_NAME"}),
FieldLogger(log_id="hello")
)
Resultaat
Als je het circus draait, zou het veld MESSAGE nu de willekeurige quote moeten bevatten:
+-------+-------------------+---------------------+-----------------------------------------------------+-------------------+---------------------+---------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+-----------------------------------------------------+-------------------+---------------------+---------------------|
| 0 | PERSON_0000000000 | 2017-01-01 01:14:12 | Become period risk wait now toward less. | PERSON_0000000158 | Ann Cruz | Victoria Washington |
| 1 | PERSON_0000000001 | 2017-01-01 01:23:51 | Month push down. | PERSON_0000000366 | Kimberly Sanchez | Steven Williams |
| 2 | PERSON_0000000002 | 2017-01-01 01:37:11 | Blue general carry else deep problem area. | PERSON_0000000613 | Bethany Smith | Frances Davis |
| 3 | PERSON_0000000003 | 2017-01-01 01:33:12 | Phone sister pretty suddenly allow conference. | PERSON_0000000086 | Frank Middleton | Ashley Fernandez |
| 4 | PERSON_0000000004 | 2017-01-01 01:08:02 | Each discussion several send wide process. | PERSON_0000000972 | Cheryl Decker | Latoya Flynn |
| 5 | PERSON_0000000005 | 2017-01-01 01:13:36 | Guess can issue writer. | PERSON_0000000030 | Thomas Rodriguez | Lindsay Bailey |
| 6 | PERSON_0000000006 | 2017-01-01 01:35:08 | Boy step claim camera common but our. | PERSON_0000000548 | James Peters | Thomas Ward |
| ... | 23993 | PERSON_0000000993 | 2017-01-02 23:25:16 | May course and kill from news. | PERSON_0000000202 | Morgan Rice | Patricia Williams |
| 23994 | PERSON_0000000994 | 2017-01-02 23:33:55 | Water door live hospital together safe. | PERSON_0000000268 | Crystal Vincent | Victoria Donovan |
| 23995 | PERSON_0000000995 | 2017-01-02 23:41:45 | Stage control management read although thousand. | PERSON_0000000466 | Dawn Kim | Ashley Baxter |
| 23996 | PERSON_0000000996 | 2017-01-02 23:39:23 | Their that region majority break article. | PERSON_0000000620 | Jimmy Franco | Alex Dominguez |
| 23997 | PERSON_0000000997 | 2017-01-02 23:45:53 | Lead simple audience response eye. | PERSON_0000000319 | Christian Christian | David Smith |
| 23998 | PERSON_0000000998 | 2017-01-02 23:29:25 | Vote man son yeah child. | PERSON_0000000487 | David Hernandez | Joshua Park |
| 23999 | PERSON_0000000999 | 2017-01-02 23:12:24 | Answer region wind condition someone bed cover. | PERSON_0000000237 | Tamara Ramirez | Beverly Rodriguez |
+-------+-------------------+---------------------+-----------------------------------------------------+-------------------+---------------------+---------------------+
Het punt van de relatie die voor de quotes is gebruikt, is dat de velden van de gegenereerde dataset nu gerelateerd zijn: sommige zinnen worden vaker gebruikt door sommige gebruikers.
Tip: bekijk de volledige code snippet op Github
Het is niet wat je doet, het is wanneer je het doet
Doel
Tot nu toe produceert elk lid van de person-populatie precies elk uur van de dag één bericht. Laten we dat wat realistischer maken.
Trumania gebruikt een combinatie van 2 concepten om het tijdsaspect van de uitvoering van een story te sturen: activiteitsniveaus en timerprofielen. Samen bepalen ze de kans om de story te triggeren, gegeven een specifieke gebruiker en tijdstip van de dag.
How-to
Je werkt de story bij en parametriseert deze twee aspecten.
Laten we eerst het timerprofiel van de story maken, dat bepaalt op welke momenten meer of minder acties worden getriggerd. Dit profiel is hetzelfde voor alle leden van de populatie die de story triggeren.
Trumania wordt geleverd met een paar interessante standaardprofielen, zoals DefaultDailyTimerGenerator, dat overeenkomt met het dagelijkse telefoonverbruik dat we hebben gemeten op een echte productiedataset:

Laten we er één in je scenario instantieren:
from trumania.components.time_patterns.profilers import DefaultDailyTimerGenerator
story_timer_gen = DefaultDailyTimerGenerator(
clock=example_circus.clock,
seed=next(example_circus.seeder))
Ten tweede zijn er de activiteit-niveaus van het populatielid voor die story, die bepalen hoe actief elke gebruiker is binnen die story. Je hebt de vrijheid om een ander niveau voor elk lid in te stellen, zodat sommige leden die actie vaker of minder vaak triggeren dan anderen.
Je zou elk niveau handmatig kunnen specificeren als je dat wilt, maar hier gebruik je gewoon een willekeurige generator die een activiteitsniveau aan elk populatielid toekent.
Eerst definiëren we drie activiteitsniveaus van gemiddeld 3, 10 en 20 triggers per dag:
low_activity = story_timer_gen.activity(n=3, per=pd.Timedelta("1 day"))
med_activity = story_timer_gen.activity(n=10, per=pd.Timedelta("1 day"))
high_activity = story_timer_gen.activity(n=20, per=pd.Timedelta("1 day"))
Vervolgens kun je de daadwerkelijke activiteitengenerator maken, opnieuw een Numpy-generator, ditmaal op basis van de methode choice. Onderstaande wijst 20% van de populatie een lage activiteit toe, 70% een gemiddelde en 10% een hoge:
activity_gen = NumpyRandomGenerator(
method="choice",
a=[low_activity, med_activity, high_activity],
p=[.2, .7, .1],
seed=next(example_circus.seeder))
En nu kun je dit timerprofiel en deze activiteitengenerator gebruiken als onderdeel van je story:
hello_world = example_circus.create_story(
name="hello_world",
initiating_population=example_circus.populations["person"],
member_id_field="PERSON_ID",
timer_gen=story_timer_gen,
activity_gen=activity_gen
)
Resultaat
De resulterende dataset heeft dezelfde schema als voorheen:
+-------+-------------------+---------------------+---------------------------------------------------------+-------------------+---------------------+----------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+---------------------------------------------------------+-------------------+---------------------+----------------------|
| 0 | PERSON_0000000016 | 2017-01-01 00:14:12 | Still try sex sure. | PERSON_0000000739 | Johnny Moore | Adam Barrett |
| 1 | PERSON_0000000063 | 2017-01-01 00:23:51 | Resource magazine wide. | PERSON_0000000511 | Cody Pham | Tina Simmons |
| 2 | PERSON_0000000064 | 2017-01-01 00:37:11 | Heat sure simple letter better forget. | PERSON_0000000044 | Katherine Fleming | Angela Gray |
| 3 | PERSON_0000000076 | 2017-01-01 00:33:12 | Star our conference always place ball. | PERSON_0000000959 | Benjamin Reese II | Barbara Alexander |
| 4 | PERSON_0000000088 | 2017-01-01 00:08:02 | Hot event five left become time commercial. | PERSON_0000000000 | Zachary Cook | Ann Cruz |
| ... |
| 28556 | PERSON_0000000962 | 2017-01-04 00:07:45 | Then include rock rule scientist condition. | PERSON_0000000448 | Johnny Washington | Melissa Adams |
| 28557 | PERSON_0000000980 | 2017-01-04 00:04:44 | Collection full argue interview property pattern never. | PERSON_0000000731 | James Ramirez | David Morrow |
| 28558 | PERSON_0000000983 | 2017-01-04 00:23:21 | High day prepare see. | PERSON_0000000509 | Evan Horne | Robert Sims |
| 28559 | PERSON_0000000988 | 2017-01-04 00:43:56 | Likely south school result case federal seat. | PERSON_0000000819 | Benjamin Campbell | Michelle Jackson DDS |
+-------+-------------------+---------------------+---------------------------------------------------------+-------------------+---------------------+----------------------+
Tip: bekijk de snippet op Github
Resultaatanalyse
De tijdspatronen die je in de configuratie van je scenario hebt opgenomen, zouden nu zichtbaar moeten zijn in de binnenstructuur van de resulterende dataset. Je kunt dat aantonen door een basisdata-analyse te doen op de resulting_dataset:
- het aantal berichten per uur van de dag zou de gespecificeerde verdeling moeten volgen (let op hoe vergelijkbaar die is met de curve van
DefaultDailyTimerGeneratorhierboven):
time_profile = (
resulting_dataset[["MESSAGE", "TIME"]]
.groupby(by=example_5_df.TIME.dt.hour)["MESSAGE"]
.count()
)
time_profile.plot()

- Het histogram van het aantal berichten per gebruiker zou ook je configuratie moeten respecteren:
- je hebt
low_activitygeconfigureerd op gemiddeld 3 berichten per dag,med_activityop 10 enhigh_activityop 20 en je draaide de simulatie over 5 dagen. Dit zou 3 groepenpersonmoeten opleveren met grofweg 15, 50 en 100 totale berichten, wat overeenkomt met de 3 centra van het histogram hieronder - je hebt de waarschijnlijkheden van deze activiteitsniveaus ingesteld op .2, .7 en .1, en dit komt opnieuw grofweg overeen met het respectievelijke oppervlak van elke "bult" van het histogram:
- je hebt
usage_per_user = resulting_dataset[["MESSAGE", "PERSON_ID"]].groupby("PERSON_ID")["MESSAGE"].count()
usage_per_user.plot(kind="hist")

Het sociale netwerk
Doel
Je kunt het scenario nog verder verbeteren door mensen met elkaar te verbinden zodat ze tijdens de uitvoering van je story berichten naar hun vrienden sturen, in plaats van naar een willekeurig iemand. Dit introduceert veel structuur in de resulterende dataset, omdat nu de sociale graaf uit de berichtenlog te achterhalen zou zijn.
How-to
Je kunt het sociale netwerk opnieuw modelleren met een Relationship, hetzelfde concept als je hierboven gebruikte voor de quotes. Je herinnert je misschien dat de quotes-relatie een gewicht had. Dat gewicht bepaalde welke quote meer of minder waarschijnlijk was, gegeven een persoon. Je kunt hier hetzelfde doen en gewichten gebruiken om te bepalen welke vrienden waarschijnlijker worden gebeld dan andere.
Merk op dat je hier een relatie van person naar person definieert, hoewel Trumania in het algemeen relaties tussen elk paar populaties mogelijk maakt.
Er zijn veel manieren om de randen van een sociale graaf te genereren. Een eenvoudige en klassieke manier is simpelweg vertrouwen op het Erdos-Renyi-algoritme. Dit is bijzonder eenvoudig in Trumania, omdat het al in het framework is ingebouwd. Je hoeft het alleen maar in je scenario te injecteren om een relatie te maken.
Hier ga je een sociale graaf definiëren tussen de leden van de person-relatie waarin elke persoon 20 vrienden heeft. Merk op dat automatisch een gewicht aan die relatie wordt toegevoegd met een Paretoverdeling.
from trumania.components.social_networks.erdos_renyi import WithErdosRenyi
# self is the circus here, see example code on github for details
self.add_er_social_network_relationship(
self.populations["person"],
relationship_name="friends",
average_degree=20)
Met deze relatie op z'n plek kun je onze story opnieuw bekijken. Dit keer wordt OTHER_PERSON niet langer willekeurig uit de populatie gekozen, maar geselecteerd uit de vrienden van elk person-lid, met een kans die evenredig is aan het gewicht dat in het sociale netwerk is gedefinieerd:
hello_world.set_operations(
self.clock.ops.timestamp(named_as="TIME"),
self.populations["person"].get_relationship("quotes")
.ops.select_one(from_field="PERSON_ID",named_as="MESSAGE"),
self.populations["person"]
.get_relationship("friends")
.ops.select_one(from_field="PERSON_ID", named_as="OTHER_PERSON"),
self.populations["person"]
.ops.lookup(id_field="PERSON_ID", select={"NAME": "EMITTER_NAME"}),
self.populations["person"]
.ops.lookup(id_field="OTHER_PERSON", select={"NAME": "RECEIVER_NAME"}),
operations.FieldLogger(log_id="hello_6")
)
Resultaat
Opnieuw heeft de resulterende dataset hetzelfde dataschema als voorheen, al bevat die nu nog meer structuur dankzij je basis sociale netwerk:
+-------+-------------------+---------------------+----------------------------------------------------+-------------------+-------------------+------------------+
| | PERSON_ID | TIME | MESSAGE | OTHER_PERSON | EMITTER_NAME | RECEIVER_NAME |
|-------+-------------------+---------------------+----------------------------------------------------+-------------------+-------------------+------------------|
| 0 | PERSON_0000000004 | 2017-01-01 00:14:12 | Clearly see sure. | PERSON_0000000321 | Cheryl Decker | Ian White |
| 1 | PERSON_0000000012 | 2017-01-01 00:23:51 | Offer or interview clear structure watch capital. | PERSON_0000000697 | Kelly Green | Ashley Turner |
| 2 | PERSON_0000000018 | 2017-01-01 00:37:11 | Recently race draw thousand around ahead. | PERSON_0000000989 | Laura Stephenson | Hannah James |
| 3 | PERSON_0000000040 | 2017-01-01 00:33:12 | To protect man image power beyond. | PERSON_0000000418 | Jeffrey Miller | Scott Collins |
... |
| 28966 | PERSON_0000000985 | 2017-01-04 00:09:23 | However agreement fear door land hotel. | PERSON_0000000211 | Laura Mason | Joshua Miller |
| 28967 | PERSON_0000000995 | 2017-01-04 00:48:08 | Top heat window quite forward friend somebody. | PERSON_0000000171 | Dawn Kim | Sandra Phillips |
| 28968 | PERSON_0000000999 | 2017-01-04 00:58:46 | Answer region wind condition someone bed cover. | PERSON_0000000252 | Tamara Ramirez | Jordan Collins |
+-------+-------------------+---------------------+----------------------------------------------------+-------------------+-------------------+------------------+
Tip: bekijk de snippet op Github
Resultaatanalyse
Een heel ruwe manier om naar dat sociale netwerk te kijken is simpelweg het aantal unieke vrienden te tellen dat door elke persoon is gebeld. Als je de simulatie lang genoeg laat lopen, zou dit je een ondergrens moeten geven van het werkelijke aantal mensen in iemands sociale netwerk (aangezien je gewichten met een Paretoverdeling gebruikt, hebben veel mensen vrienden die echt onwaarschijnlijk worden gebeld, dus je moet de simulatie heel lang laten lopen om elke mogelijke gebelde persoon echt te observeren).
Je voert ons scenario 15 gesimuleerde dagen uit en kreeg dit, dat heel dicht bij en iets onder de gemiddelde graad van 20 ligt die je in je sociale graaf hebt ingesteld:
social_group_size = (
resulting_dataset[["PERSON_ID", "OTHER_PERSON"]]
.groupby("PERSON_ID")
.agg(lambda friends_ids: len(friends_ids.unique()))
)
social_group_size.plot(kind="hist")

Er is meer!
Verschillende features van Trumania zijn in deze post niet beschreven:
- meerdere populaties in een scenario
- meerdere stories in een scenario
- probabilistische afhankelijkheden tussen stories toevoegen (bijv. iemand belt een vriend nadat hij is gebeld)
- state-updates (bijv. balans verlagen op basis van belduur)
- herbruikbare stukjes scenario, zoals geografieën, meer timerprofielen, ...
- persistentie van stukjes scenario
Conclusie
We hebben Trumania geïntroduceerd als een scenario-gebaseerde datageneratorbibliotheek in Python. De gegenereerde datasets kunnen worden gebruikt voor een breed scala aan toepassingen zoals testen, leren en benchmarken.
We legden uit dat we, om een toepassing of algoritme goed te testen, datasets nodig hebben die bepaalde verwachte statistische eigenschappen respecteren. We illustreerden dat Trumania daartoe in staat is in een voorbeeld waarin we een basis-"message log"-dataset genereerden die een verdeling respecteert van het aantal berichten per gebruiker, een verdeling naar tijdstip van de dag en een verdeling van oproepen binnen een sociale graaf.
We hopen ook te hebben laten zien dat de flexibiliteit van Trumania-componenten het mogelijk maakt om een breed scala aan scenario's te creëren.
Een Trumania-scenario kan relaties tussen entiteiten bevatten en kan worden geconfigureerd met theoretische willekeurige verdelingen, empirische verdelingen die zijn waargenomen in een echte dataset of zelfs echte productiedata (bijv. een dataset met locatie-id's of iets dergelijks). We demonstreerden dat door een statistische verdeling van telefoongebruikstimestamps, waargenomen in een echte dataset, te hergebruiken voor het configureren van de random timer van onze story.
Met dit in het achterhoofd: leef je uit! Ga naar Trumania's Github en schrijf je eigen scenario ;)
Neem anders gerust contact op met Svend Vanderveken of co-auteur Milan van der Meer:
- Svend Vanderveken is freelance software engineer met expertise in dataverwerkingsoplossingen, vooral in Kafka, Scala, SQL, Python... Volg hem via zijn blog svend.kelesia.com en twitter @sv3ndk.
- Milan van der Meer werkt momenteel als software engineer voor Real Impact Analytics. Je kunt altijd connecten met hem op LinkedIn
