← Return to the example · Supporting files

subgraph OxfordUS Portfolio

Saved notebook · 106 cells. Code and outputs below are retained from the original; nothing was executed for this reading copy. Environment dependencies, missing data and the model limitations remain as described in the linked guide.

Cell 1 · markdown

Setting up a Graph Database for the US Government IT Portfolios

Cell 2 · code

# Privacy edit, 4 October 2026: personal filesystem paths were replaced by neutral example paths.
# Existing calculations and saved results were not rerun; this file is a privacy-edited reading edition.
import pandas as pd
import numpy as np
import dabl
%matplotlib inline
import os
from dabl import plot
import matplotlib.pyplot as plt
import datetime as dt
Saved output
Matplotlib is building the font cache; this may take a moment.

Cell 3 · code

directory = 'example-data/2013/'

Cell 4 · markdown

Bring in Projects file

Cell 5 · code

Projects=pd.read_csv(directory+'Projects.csv', encoding= 'unicode_escape')

Cell 6 · code

Projects=Projects.drop(['Agency Project ID','Project Description','Lifecycle Cost', 'Cost Variance (%)','Projected/Actual Cost ($ M)','Updated Time','Unique Project ID'],axis=1)

Cell 7 · code

Projects['Completion Date (B1)']=Projects['Completion Date (B1)'].astype('datetime64')
Projects['Planned Project Completion Date (B2)']=Projects['Planned Project Completion Date (B2)'].astype('datetime64')
Projects['Start Date']=Projects['Start Date'].astype('datetime64')
Projects['Projected/Actual Project Completion Date (B2)']=Projects['Projected/Actual Project Completion Date (B2)'].astype('datetime64')
Projects['Updated Date']=Projects['Updated Date'].astype('datetime64')
Projects['Business Case ID']=Projects['Business Case ID'].astype('category')
Projects['Agency Code']=Projects['Agency Code'].astype('category')
Projects['Project ID']=Projects['Project ID'].astype('category')

Cell 8 · markdown

In nearly all cases, Completion Date (B1) > Planned Project Completion Date (B2)

In some cases ‘Projected/Actual Project Completion Date (B2) < Planned Project Completion Date (B2) But in most cases, it is the other way around , so that Planned < Project /Actual i,e is earlier

And in most cases, where not equal: Projected/Actual Project Completion Date (B2)’ < ‘Completion Date (B1)’

therefore, Planning progresses in this order

  1. Planned Project Completion Date (B2)
  2. Projected/Actual Project Completion Date (B2)
  3. Completion Date (B1)

So I will create:

Cell 9 · code

# worked this out by a variety of boolean mixing such as:
# constraint=(Projects['Projected/Actual Project Completion Date (B2)']<Projects['Completion Date (B1)'])&(pd.notna(Projects['Projected/Actual Project Completion Date (B2)']))&(pd.notna(Projects['Completion Date (B1)']))
# Projects [constraint]

Cell 10 · markdown

Projected/Actual more or less the same as Planned cost.

Lifecycle cost is:

Cell 11 · code

# worked this out from variants of: 
# plt.scatter ((Projects['Planned Cost ($ M)'],Projects['Cost Variance ($ M)']))

Cell 12 · code

Projects['Planned_Duration']=(Projects['Planned Project Completion Date (B2)']-Projects['Start Date']).dt.days
Projects['Project_delay']=(np.maximum(Projects['Projected/Actual Project Completion Date (B2)']-Projects['Planned Project Completion Date (B2)'],Projects['Completion Date (B1)']-Projects['Planned Project Completion Date (B2)'])).dt.days

Cell 13 · code

Projects=Projects.drop(['Planned Project Completion Date (B2)','Completion Date (B1)','Projected/Actual Project Completion Date (B2)'],axis=1)

Cell 14 · code

Projects['Start_days_after_2000']=(Projects['Start Date']-pd.Timestamp('2000-01-01')).dt.days

Cell 15 · code

Projects=Projects.drop(['Schedule Variance (%)','Schedule Variance (in days)'],axis=1)

Cell 16 · code

Projects['Updated Date']=(Projects['Updated Date']-Projects['Start Date']).dt.days # where this is now the no of days the update happened after start date

Cell 17 · code

Constraint=Projects['Agency Code']==9

Cell 18 · code

Projects=Projects[Constraint]

Cell 19 · markdown

Work out Data Structure as you go.

Agency » Investment (w Business Case ) » Project ( Start, Completion, Cost, Variances)

Create a basic example in Neo4j inline

CREATE (m:Agency {name:’Dept Agriculture’,Code:5}) CREATE (n:Investment {name:’AMS Infrastructure WAN and DMZ (AMSWAN)’, Identifier:’005-000001723’}) CREATE (o:Project {name:’Virtualization’, Id:657,Cost_Variance:0,Planned_Cost:0.179,Updated_Date:60,Planned_Duration:182, Project_delay:0,Start_days_after_2000:4291}) CREATE (m)-[:invests]->(n) CREATE (n)-[:pays_for]->(o)

After testing this, I can bring in CSV: see further below

Cell 20 · markdown

Export to CSV en route to Neo4j

Cell 21 · code

Projects=Projects.rename(columns={"Agency Name": "Agency_Name", "Agency Code":"Agency_Code",'Investment Title':'Investment_Title','Unique Investment Identifier':'Unique_Investment_Identifier','Project Name':'Project_Name','Project ID':'Project_ID','Start Date':'Start_Date', 'Cost Variance ($ M)':'Cost_Variance','Planned Cost ($ M)':'PlannedCost','Updated Date':'Updated_Date','Business Case ID':'Business_Case_ID'})  

Cell 22 · code

Projects=Projects.dropna()

Cell 23 · code

Projects.to_csv(directory+'Health_Projects_Out.csv', index=False)

Cell 24 · markdown

#Cypher code for Neo4j LOAD CSV WITH HEADERS FROM ‘file:///Health_Projects_Out.csv’ AS row MERGE (m:Agency {name:row.Agency_Name,Code:row.Agency_Code}) MERGE (n:Investment {name:row.Investment_Title,Identifier:row.Unique_Investment_Identifier,Business_Case:row.Business_Case_ID}) MERGE (o:Project {name:row.Project_Name,Id:row.Project_ID,Cost_Variance:toFloat(row.Cost_Variance),Planned_Cost:toFloat(row.PlannedCost),Updated_Date:toInteger(row.Updated_Date),Planned_Duration:toInteger(row.Planned_Duration),Project_delay:toInteger(row.Project_delay),Start_days_after_2000:toInteger(row.Start_days_after_2000)}) MERGE (m)-[:invests]->(n) MERGE (n)-[:pays_for]->(o)

Cell 25 · markdown

Pull in strategic investment information

Cell 26 · code

Strategy=pd.read_csv(directory+'Exhibit300A.csv', encoding= 'unicode_escape')

Cell 27 · code

Strategy=Strategy.drop(['Investment Title (Exhibit 53)','Investment Title (Exhibit 300)','Date of Last Change to Activities','Date of Last Update to Activities','Date of Last Update to Activities','Date of Last Update to Activities','Date of Last Change to Contracts','Date of Last Change to Performance Metrics','Date of Last Tech Stat','Budget Year','Date of Last Investment Detail Update','Investment Auto Submission Date'],axis=1)

Cell 28 · code

Strategy=Strategy.drop(['Data Freshness','Date of Last Change to CIO Evaluation','Date of Last Update to CIO Evaluation'],axis=1)

Cell 29 · code

Strategy=Strategy.drop(['IPT Charter Date','Date Investment First Submitted'],axis=1)

Cell 30 · code

Strategy=Strategy.drop(['Business Case ID'],axis=1)

Cell 31 · code

Strategy=Strategy.drop(['CIO Evaluation Color'],axis=1)

Cell 32 · code

Strategy['Date of Last Baseline']=Strategy['Date of Last Baseline'].astype('datetime64')

Cell 33 · code

#Strategy=Strategy.dropna()

Cell 34 · code

Strategy=Strategy.rename(columns={'Agency Name':'Agency_Name',"Bureau Name": "Bureau_Name",'Bureau Code':'Bureau_Code','Number of changes to Baseline':'Number_of_changes_to_Baseline','Evaluation (by Agency CIO)':'Evalu)ation_by_CIO"}'})

Cell 35 · code

Strategy=Strategy.rename(columns={'Evalu)ation_by_CIO"}':'Evaluation_by_CIO','Date of Last Baseline':'Date_Last_Baseline'})

Cell 36 · code

Constraint=Strategy['Agency Code']==9

Cell 37 · code

Strategy=Strategy[Constraint]

Cell 38 · markdown

Work out Data Structure as you go.

This results in the following Cypher code to be placed into Neo4j

LOAD CSV WITH HEADERS FROM ‘file:///Health_Strategy_Out.csv’ AS row MERGE (m:Agency {Code:row.Agency_Code}) MERGE (o:Bureau {Code:row.Bureau_Code,Name:row.Bureau_Name}) MERGE (n:Investment {Identifier:row.Unique_Investment_Identifier}) CREATE (m)-[:owns]->(o) CREATE (o)-[:responsible_for]->(n) SET n.Summary=row.Brief_Summary SET n.Performance_Gap=row.Summary_of_Performance_Gap SET n.Evaluation_by_CIO=row.Evaluation_by_CIO SET n.Changes_to_Baseline=row.Number_of_changes_to_Baseline SET n.Last_Baseline=row.Date_Last_Baseline

LOAD CSV WITH HEADERS FROM ‘file:///Health_Strategy_Out.csv’ AS row MATCH (m:Agency {Code:row.Agency_Code}) SET m.name = row.Agency_Name RETURN m

“MERGE matches on the entire pattern you specify within a single clause…The solution is to MERGE on the unique property and then use SET to update additional properties.

Cell 39 · code

Cell 40 · code

Strategy=Strategy.rename(columns={'Unique Investment Identifier':'Unique_Investment_Identifier','Brief Summary':'Brief_Summary',
'Summary of Performance Gap':'Summary_of_Performance_Gap','Agency Code':'Agency_Code'})

Cell 41 · code

Strategy.to_csv(directory+'Health_Strategy_Out.csv', index=False)

Cell 42 · markdown

Bring in Contracts file

Cell 43 · code

Contracts=pd.read_csv(directory+'Contracts.csv', encoding= 'unicode_escape')

Cell 44 · code

Constraint=Contracts['Agency Code']==9

Cell 45 · code

Contracts=Contracts[Constraint]

Cell 46 · code

Contracts=Contracts.drop(['Business Case ID','Agency Code','Agency Name','Investment Title','Agency Contract ID','Contract Status','Contracting Agency ID','Contract Number (PIID)','Performance Based Contract (USAspending)','Contract Start Date (USAspending)','Contract End Date (USAspending)','Contract Compete (USAspending)','Base Contract ID (USAspending)'
        ,'Identifying Agency ID (USAspending)','Transaction Number (USAspending)','Timestamp (Base Contract)'],axis=1)

Cell 47 · code

Contracts=Contracts.drop(['IDV Agency ID','Match found in USAspending','Solicitation ID (USAspending)'],axis=1)

Cell 48 · code

Contracts=Contracts.rename(columns={'Unique Investment Identifier':'Unique_Investment_Identifier','Contract ID':'Contract_ID','IDV PIID':'IDV_PIID','Vendor Name (USAspending)':'Vendor_name','Action Obligation Amount (In $ million) (USAspending)':'Contract_size','Contract Description (USAspending)':'Contract_Description'})

Cell 49 · markdown

Work out Data Structure as you go.

This results in the following Cypher code to be placed into Neo4j

Cell 50 · code

Contracts=Contracts.dropna()

Cell 51 · code

Contracts.to_csv(directory+'Health_Contracts_Out.csv', index=False)

Cell 52 · markdown

LOAD CSV WITH HEADERS FROM ‘file:///Health_Contracts_Out.csv’ AS row MERGE (m:Contract {ID:row.Contract_ID}) MERGE (n:Investment {Identifier:row.Unique_Investment_Identifier}) MERGE (o:Supplier {ID:row.IDV_PIID}) SET m.Description=row.Contract_Description SET m.Contract_size=row.Contract_size SET o.name=row.Vendor_name CREATE (n)-[:lets_contract]->(m) CREATE (n)-[:uses_Supplier]->(o) CREATE (o)-[:delivers]->(m)

Cell 53 · markdown

Bring in Metrics file

Cell 54 · code

Metrics=pd.read_csv(directory+'Performance_Metrics.csv', encoding= 'unicode_escape')

Cell 55 · code

Metrics=Metrics.drop(['Business Case ID','Agency Name','Agency Performance Metric ID','Comment','Updated Date','Updated Time','Reporting Frequency'],axis=1)

Cell 56 · code

Metrics=Metrics.drop(['Target for PY','Actual for PY','Most Recent Actual Results','Measurement Condition'],axis=1)

Cell 57 · code

Metrics=Metrics.rename(columns={'Unique Investment Identifier':'Unique_Investment_Identifier','Performance Metric ID':'Performance_Metric_ID','Metric Description':'Metric_Description','Unit of Measure':'Unit_of_Measure','FEA Performance Measurement Category Mapping':'Measurement_category','Actuals have Met/Not Met Target':'Metric_results'})

Cell 58 · code

#Metrics=Metrics.dropna()

Cell 59 · code

Constraint=Metrics['Agency Code']==9

Cell 60 · code

Metrics=Metrics[Constraint]

Cell 61 · code

Metrics.to_csv(directory+'Health_Metrics_Out.csv', index=False)

Cell 62 · markdown

from this, we know that Metric ID is unique constraint=(Metrics.duplicated(subset=’Performance_Metric_ID’, keep=False))==True Metrics[constraint]

Cell 63 · markdown

Work out Data Structure as you go.

This results in the following Cypher code to be placed into Neo4j

LOAD CSV WITH HEADERS FROM ‘file:///Health_Metrics_Out.csv’ AS row MERGE (m:Metric {ID:row.Performance_Metric_ID}) MERGE (n:Investment {Identifier:row.Unique_Investment_Identifier}) SET m.Description=row.Metric_Description SET m.Measurement_category=row.Measurement_category SET m.Metric_results=row.Metric_results MERGE (n)-[:judged_by]->(m)

Cell 64 · markdown

Bring in Business Mapping

Cell 65 · code

Mapping=pd.read_csv(directory+'Exhibit53.csv', encoding= 'unicode_escape')

Cell 66 · code

Constraint=Mapping['Agency Code']==9

Cell 67 · code

Mapping=Mapping[Constraint]

Cell 68 · code

Mapping=Mapping.drop(['Agency Code','Agency Name','Previous UPI','Investment Category','Bureau Code','Bureau Name','Part of Exhibit 53'],axis=1)

Cell 69 · code

Mapping=Mapping.drop(['Mission Delivery And Management Support Area','Line Item Descriptor','Investment Title','Investment Description','Updated Date'],axis=1)

Cell 70 · code

Mapping=Mapping.drop(['Budget Year','XML Request ID','Updated Time','O&M BY Contributions ($ M)','O&M BY Agency Funding ($ M)','O&M CY Contributions ($ M)'],axis=1)

Cell 71 · code

Mapping=Mapping.drop(['O&M CY Agency Funding ($ M)','O&M PY Contributions ($ M)','O&M PY Agency Funding ($ M)','DME BY Contributions ($ M)'],axis=1)

Cell 72 · code

Mapping=Mapping.drop(['Segment Architecture - Agency Segment','Total IT Spending FY2011 (PY) ($ M)','Total IT Spending FY2012 (CY) ($ M)'],axis=1)

Cell 73 · code

Mapping=Mapping.drop(['DME PY Contributions ($ M)','DME CY Contributions ($ M)','DME BY Agency Funding ($ M)'],axis=1)

Cell 74 · code

Mapping=Mapping.rename(columns={'Unique Investment Identifier':'Unique_Investment_Identifier','Type of Investment':'Investment_type'})

Cell 75 · code

Mapping=Mapping.rename(columns={'FEA BRM Mapping - Sub-Function':'Sub-Function','FEA BRM Mapping - Primary Function':'Function','FEA BRM Mapping - Business Area':'Business_Area'})

Cell 76 · code

Mapping=Mapping.rename(columns={'Service Code Mapping - Component':'Service_Component','Service Code Mapping - Primary Function':'Service','Function':'Business Function'})

Cell 77 · code

Mapping=Mapping.rename(columns={'Service Code Mapping - Business Area':'Service Area','Segment Architecture - Federal Standard Segment':'Architecture_code'})

Cell 78 · code

Mapping=Mapping.drop(['Total IT Spending FY2013 (BY) ($ M)','DME PY Agency Funding ($ M)'],axis=1)

Cell 79 · code

Mapping=Mapping.rename(columns={'DME CY Agency Funding ($ M)':'Enhancement_spend_$m'})

Cell 80 · code

Mapping=Mapping.rename(columns={'Enhancement_spend_$M)':'Enhancement_spend_$m'})

Cell 81 · code

Mapping=Mapping.rename(columns={'Service Area':'Service_Area'})

Cell 82 · code

Mapping=Mapping.rename(columns={'Business Function':'Business_Function'})

Cell 83 · code

#Mapping=Mapping.dropna()

Cell 84 · code

Mapping.to_csv(directory+'Health_Mapping_Out.csv', index=False)

Cell 85 · markdown

Code for Neo4j

LOAD CSV WITH HEADERS FROM ‘file:///Health_Mapping_Out.csv’ AS row MERGE (n:Investment {Identifier:row.Unique_Investment_Identifier}) SET n.Enhancement_spend_$m=toFloat(row.Enhancement_spend_$m) MERGE (o:Service {name:row.Service}) MERGE (p:Business_Area {name:row.Business_Area}) MERGE (q:Business_Function {name:row.Business_Function}) SET n.Investment_type=row.Investment_type MERGE (n)-[:has_business_function]->(q) MERGE (q)-[:serves]->(p) MERGE (n)-[:is_a_type_of]->(o)

Cell 86 · markdown

Bring in activities

Cell 87 · code

Activities=pd.read_csv(directory+'Activities.csv', encoding= 'unicode_escape')

Cell 88 · code

Activities=Activities.drop(['Agency Name','Business Case ID','Investment Title','Date of Last Change','Baseline ID'],axis=1)

Cell 89 · code

Activities=Activities.drop(['Cost Variance','Cost Variance Percent'],axis=1)

Cell 90 · code

# Cost Variance and Cost % are unreliable: they give different signs, so dropped

Cell 91 · code

Activities=Activities.drop(['Structure ID','Schedule Variance (in days)','Schedule Variance Percent','Schedule Duration (in days)'],axis=1)

Cell 92 · code

Constraint=Activities['Agency Code']==9

Cell 93 · code

Activities=Activities[Constraint]

Cell 94 · code

Activities=Activities.dropna(subset=['Unique Investment Identifier','Agency Code','Agency Activity ID','Project ID','Activity Name','Start Date Planned','Completion Date Planned',
                                     'Total Costs Planned','Activity Status','Has No Child Activity','Date of Last Update','Unique Activity ID'])

Cell 95 · code

Activities=Activities.drop(['Agency Activity ID'],axis=1)

Cell 96 · code

Activities['Start Date Planned'] = pd.to_datetime(Activities['Start Date Planned'])
Activities['Start Date Projected'] = pd.to_datetime(Activities['Start Date Projected'])
Activities['Start Date Actual'] = pd.to_datetime(Activities['Start Date Actual'])
Activities['Completion Date Planned'] = pd.to_datetime(Activities['Completion Date Planned'])
Activities['Completion Date Projected'] = pd.to_datetime(Activities['Completion Date Projected'])
Activities['Completion Date Actual'] = pd.to_datetime(Activities['Completion Date Actual'])

Cell 97 · code

Activities['Planned_Duration']=np.subtract(Activities['Completion Date Planned'],Activities['Start Date Planned'])

Cell 98 · code

Activities=Activities.drop('Completion Date Planned',axis=1)

Cell 99 · code

Activities['Actual_duration']=np.subtract(Activities['Completion Date Actual'],Activities['Start Date Actual'])
Activities['Actual_start_delay']=np.subtract(Activities['Start Date Actual'],Activities['Start Date Planned'])

Cell 100 · code

Activities=Activities.drop(['Start Date Actual','Completion Date Actual'],axis=1)

Cell 101 · code

Activities=Activities.rename(columns={'Unique Investment Identifier':'Unique_Investment_Identifier','Project ID':'Project_ID','Activity Name':'Activity_Name','Activity Description':'Activity Description','Key Deliverable / Usable Functionality':'Output_type','Start Date Planned':'Start Date Planned','Total Costs Planned':'Planned_Costs','Activity Status':'Status','Has No Child Activity':'No_Child_Activity','Unique Activity ID':'ID'})

Cell 102 · code

Activities=Activities.drop(['Unique_Investment_Identifier','Agency Code','Start Date Projected','Start Date Projected'],axis=1)

Cell 103 · code

Activities=Activities.rename(columns={'Activity Description':'Description'})

Cell 104 · code

Activities=Activities.rename(columns={'Start Date Planned':'Start_Date_Planned'})

Cell 105 · markdown

Code for Neo4j

LOAD CSV WITH HEADERS FROM ‘file:///Health_Activities_Out.csv’ AS row MERGE (n:Project {Id:row.Project_ID}) MERGE (o:Activity {Id:row.ID}) SET o.name=row.Activity_Name SET o.description=row.Description SET o.output_type=row.Output_type SET o.planned_Costs=row.Planned_Costs SET o.status=row.Status SET o.No_Child_Activity=row.No_Child_Activity SET o.Planned_Duration=row.Planned_Duration SET o.Actual_duration=row.Actual_duration SET o.Actual_start_delay=row.Actual_start_delay SET o.Start_Date_Planned=row.Start_Date_Planned MERGE (n)-[:has_task]->(o)

This is the additional code to take subgraph, as a query, to export Cypher code, say for populating a Sandbox

CALL apoc.export.cypher.query( “MATCH (n:Bureau)-[a]-(o:Investment)-[b]-(p:Project)-[c]-(q:Activity),(o)-[d]-(r:Service),(o)-[e]-(s:Business_Function)-[f]-(t:Business_Area),(o)-[g]-(u:Metric),(o)-[h]-(v:Agency) WHERE n.Name=’Small Business Administration’ OR RETURN *”,”export.cypher”,{});

Cell 106 · code

Activities.to_csv(directory+'Health_Activities_Out.csv', index=False)