Which Cycle Frontier Jobs Are Worth Doing? - Part Three.

Let’s do the calculations!
game
python
data
science
exploration
cycle
frontier
Published

October 14, 2022

This is the third post in a series about collecting, cleaning and analyzing which Jobs are worth doing in The Cycle: Frontier game. If you’ve found this post before the others I would recommend the other posts first to catch up: Post One and Post Two. We’ll pick up where we left off having separate datasets for the faction rewards as well as all the loot. Let’s get our imports!

import pandas as pd             # for the data.
import numpy as np              # for a NaN type
import matplotlib.pyplot as plt # For plotting, and some customization of plots.
import seaborn as sns           # For pretty plots.
import requests as r            # For downloading from websites

# Fix the size of the graphs
sns.set(rc={"figure.figsize":(11, 8)})

There are two tasks we’ll need to adjust before we continue. The first is that our Tasks work was only for one faction so we’ll need to loop through each faction’s tasks and then append them all together into a single dataset; the code is included simply for observation.

def breakLoot(taskString, index=0):
    parts = taskString.split(' ', maxsplit=1)
    if index == 0:
        return int(parts[index])
    elif index == 1:
        return parts[index]
    else:
        # This shouldn't be called.
        return None

tasks = []

for index in range(0,3):
    tasksSubset = siteJobs[index][["Name", "Description", "Tasks"]].copy()
    tasksSubset = tasksSubset[ ~tasksSubset.Tasks.isna()]
    tasksSubset = tasksSubset[ ~tasksSubset.Tasks.str.contains("Kill")]

    regex = r"(\d+\s[\w]+\s[\w]+)"
    tmp = tasksSubset.Tasks.str.extractall(regex)

    count = tmp.reset_index()[0].apply(breakLoot).values
    aLoot = tmp.reset_index()[0].apply(breakLoot, index=1).values

    tmp = tmp.assign(
        count = count,
        loot = aLoot
    )

    nameDescriptSlice = tasksSubset.loc[tmp.reset_index()["level_0"], ['Name', 'Description']]

    tmp = tmp.assign(
        name = nameDescriptSlice.Name.values,
        description = nameDescriptSlice.Description.values
    )

    taskSlice = tmp.reset_index().drop([
        'level_0',
        'match',
        0
    ], axis =1 )

    taskSlice = taskSlice[['name', 'count', 'loot', 'description']]
    tasks.append(taskSlice)
tasks = pd.concat([*tasks])

The second task is that we need a single dataset for all the job rewards. We’ll definitely want to tag each so we don’t lose track of which task belongs to which Faction.

# This is new; stop inserting this trash:
# want the faction when we join them all:
korolevRewards['Faction'] = "Korolev"
icaRewards['Faction'] = "ICA"
osirisRewards['Faction'] = "Osiris"

allJobs = pd.concat([korolevRewards, icaRewards, osirisRewards])

# Proof they're all in there:
allJobs.groupby('Faction').take([0])
Units Rewards Job
Faction
ICA 1 4400 K-Marks Water Filtration System
Korolev 1 3800 K-Marks New Mining Tools
Osiris 1 2200 K-Marks Lab equipment

For this analysis, we’re really only interested in the Kmarks so we’ll need to only pull those rows; we’ll do some work with the others later but for now just the money.

lootKMarks = loot.query('Name == "K-Marks"')
lootKMarks.head(8)
Unit Name Rarity Personal Quarters Campaigns Jobs Printing Loot
1 150.0 K-Marks Common Yes x11 Yes x10 Yes No Flawed Veltecite
6 570.0 K-Marks Uncommon Yes x8 Yes x14 Yes No Cloudy Veltecite
11 854.0 K-Marks Rare Yes x11 Yes x25 Yes No Clear Veltecite
16 1922.0 K-Marks Epic Yes x7 Yes x9 Yes No Pure Veltecite
21 6487.0 K-Marks Legendary Yes x4 Yes x2 Yes No Veltecite Heart
26 570.0 K-Marks Uncommon Yes x36 Yes x7 Yes No Brittle Titan Ore
31 854.0 K-Marks Rare Yes x6 Yes x8 Yes No Titan Ore
36 150.0 K-Marks Common Yes x46 Yes x1 Yes No Nickel

Joins are a complicated topic which I’m not going to flesh out here. In this instance, what we want is the loot table with the Kmark value connected to the jobs table with respect to the name of the loot. And, we’ll tell Pandas to join those together below making sure that as long as it exists in the left table that it gets connected to something in the right table.

taskLoot = tasks.merge(lootKMarks[['Loot', 'Unit']], left_on='loot', right_on='Loot', how='outer')
taskLoot.head()
name count loot description Loot Unit
0 New Mining Tools 2.0 Hydraulic Piston We are producing new Mining Tools for new Pros... Hydraulic Piston 338.0
1 Excavator Improvements 3.0 Hydraulic Piston The suspension on our mining excavators need i... Hydraulic Piston 338.0
2 New Mining Tools 10.0 Hardened Metals We are producing new Mining Tools for new Pros... Hardened Metals 150.0
3 Automated Security 16.0 Hardened Metals We will have to build new turrets to help prot... Hardened Metals 150.0
4 Air Lock Upgrades 12.0 Hardened Metals Our engineers designed a safer airlock system ... Hardened Metals 150.0

We’ll now multiply the count of the loot times their values to get the cost per resource in the task.

taskLoot['Cost'] = taskLoot['count'] * taskLoot['Unit']
taskLoot.head()
name count loot description Loot Unit Cost
0 New Mining Tools 2.0 Hydraulic Piston We are producing new Mining Tools for new Pros... Hydraulic Piston 338.0 676.0
1 Excavator Improvements 3.0 Hydraulic Piston The suspension on our mining excavators need i... Hydraulic Piston 338.0 1014.0
2 New Mining Tools 10.0 Hardened Metals We are producing new Mining Tools for new Pros... Hardened Metals 150.0 1500.0
3 Automated Security 16.0 Hardened Metals We will have to build new turrets to help prot... Hardened Metals 150.0 2400.0
4 Air Lock Upgrades 12.0 Hardened Metals Our engineers designed a safer airlock system ... Hardened Metals 150.0 1800.0

Now we’ll just group by the name of the task to and take the sume of each to get the total cost per task.

taskLoot[['name', 'Cost']].groupby('name').sum().head()
Cost
name
A Craving 4048.0
A Solution 10125.0
A new Energy Source 0.0
A new type of Alloy 1800.0
Air Lock Upgrades 3900.0

This is the first piece we’ll need to get the results we’re after; the other part is all the jobs we collected in the previous post - and brought here.

results = allJobs.query('Rewards == "K-Marks"').merge(
    taskLoot[['name', 'Cost']].groupby('name').sum(), left_on="Job", right_on="name", how='left')
results.head()
Units Rewards Job Faction Cost
0 3800 K-Marks New Mining Tools Korolev 2176.0
1 11000 K-Marks Explosive Excavation Korolev 6836.0
2 6900 K-Marks Mining Bot Korolev 1014.0
3 7600 K-Marks None of your Business Korolev 1800.0
4 10000 K-Marks Insufficient Processing Power Korolev 3845.0

Now we have the rewards and the cost per task we can finally calculate the Balance of each job!

results['Balance'] = results['Units'] - results['Cost']
results.groupby('Faction').take([0,3])
Units Rewards Job Cost Balance
Faction
ICA 46 4400 K-Marks Water Filtration System 2700.0 1700.0
49 12000 K-Marks Air Lock Upgrades 3900.0 8100.0
Korolev 0 3800 K-Marks New Mining Tools 2176.0 1624.0
3 7600 K-Marks None of your Business 1800.0 5800.0
Osiris 94 2200 K-Marks Lab equipment 676.0 1524.0
97 26000 K-Marks Cretins 6300.0 19700.0

Conclusions - and Questions!

So, now that we have the data let’s work on some questions!

Given any particular faction, which task has the highest Balance?

balanceMax = results.Balance.max()
results.loc[results.Balance == balanceMax]
Units Rewards Job Faction Cost Balance
91 227000 K-Marks Striking Big ICA 0.0 227000.0

Looks like it is the Job Striking Big. What’s this job about?: > Damn it! We ran out of Fuel for our Radiation Shields here on the Station. And there’s no replacement for Nanite Infused Crude Oil. You know what to do.

It’s collecting Oil Cannisters! Those are worth a lot of money so it’s not a surprise that a task which wants you to collect them would also pay out so highly.

What about the job that pays the least?

balanceMin = results.Balance.min()
results.loc[results.Balance == balanceMin]
Units Rewards Job Faction Cost Balance
94 2200 K-Marks Lab equipment Osiris 676.0 1524.0

Well, it our good friends Osiris failing the station again. That looks like a low level job from a cost and reward that low. Not really much of a surprise here.

So, what about the mean balance - and how many tasks are there which reward more than that average value?

balanceMean = results.Balance.mean()
f"${round(balanceMean, 2)}"
'$20600.89'
len( results.loc[results.Balance >= balanceMean])
40
# How many are there per faction?
results.loc[
    results.Balance >= balanceMean ]\
        .groupby('Faction')\
        .count()\
        .reset_index()\
        .rename({'Units':'Count'}, axis=1)[[
    'Faction', 'Count'
]]
Faction Count
0 ICA 14
1 Korolev 14
2 Osiris 12

And, unsurpisingly Osiris is slightly behind but they’re mostly the same.

Ok, so what’s the cost of the Job that inspired all this work: And Two Smoking Barrels?

results.loc[results.Job.str.contains("Barrel")]
Units Rewards Job Faction Cost Balance
39 19000 K-Marks And two smoking Barrels Korolev 0.0 19000.0

Wait a minute! There should be a cost here and it is missing! Well, that’s because there are no guns in the loot table. So, there is no cost when we connect the data together. And, after some further checking there are some missing values here. That quest we checked above with the Oil? They’re not named the same betwee the tables: NiC Oil vs NiC Oil Cannister. It’s not the only one like this either.

Looks like we’ve got more work to do.