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
set(rc={"figure.figsize":(11, 8)}) sns.
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!
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):
= taskString.split(' ', maxsplit=1)
parts 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):
= siteJobs[index][["Name", "Description", "Tasks"]].copy()
tasksSubset = tasksSubset[ ~tasksSubset.Tasks.isna()]
tasksSubset = tasksSubset[ ~tasksSubset.Tasks.str.contains("Kill")]
tasksSubset
= r"(\d+\s[\w]+\s[\w]+)"
regex = tasksSubset.Tasks.str.extractall(regex)
tmp
= tmp.reset_index()[0].apply(breakLoot).values
count = tmp.reset_index()[0].apply(breakLoot, index=1).values
aLoot
= tmp.assign(
tmp = count,
count = aLoot
loot
)
= tasksSubset.loc[tmp.reset_index()["level_0"], ['Name', 'Description']]
nameDescriptSlice
= tmp.assign(
tmp = nameDescriptSlice.Name.values,
name = nameDescriptSlice.Description.values
description
)
= tmp.reset_index().drop([
taskSlice 'level_0',
'match',
0
=1 )
], axis
= taskSlice[['name', 'count', 'loot', 'description']]
taskSlice
tasks.append(taskSlice)= pd.concat([*tasks]) 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:
'Faction'] = "Korolev"
korolevRewards['Faction'] = "ICA"
icaRewards['Faction'] = "Osiris"
osirisRewards[
= pd.concat([korolevRewards, icaRewards, osirisRewards])
allJobs
# Proof they're all in there:
'Faction').take([0]) allJobs.groupby(
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.
= loot.query('Name == "K-Marks"')
lootKMarks 8) lootKMarks.head(
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.
= tasks.merge(lootKMarks[['Loot', 'Unit']], left_on='loot', right_on='Loot', how='outer')
taskLoot 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.
'Cost'] = taskLoot['count'] * taskLoot['Unit']
taskLoot[ 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.
'name', 'Cost']].groupby('name').sum().head() taskLoot[[
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.
= allJobs.query('Rewards == "K-Marks"').merge(
results 'name', 'Cost']].groupby('name').sum(), left_on="Job", right_on="name", how='left')
taskLoot[[ 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!
'Balance'] = results['Units'] - results['Cost']
results['Faction').take([0,3]) results.groupby(
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?
= results.Balance.max()
balanceMax == balanceMax] results.loc[results.Balance
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?
= results.Balance.min()
balanceMin == balanceMin] results.loc[results.Balance
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?
= results.Balance.mean()
balanceMean f"${round(balanceMean, 2)}"
'$20600.89'
len( results.loc[results.Balance >= balanceMean])
40
# How many are there per faction?
results.loc[>= balanceMean ]\
results.Balance 'Faction')\
.groupby(\
.count()\
.reset_index()'Units':'Count'}, axis=1)[[
.rename({'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?
str.contains("Barrel")] results.loc[results.Job.
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.