EIS metadata validation script

Used to validate Planon output with spreadsheet input

1. Data import


In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

Read data. There are two datasets: Planon and Master. The latter is the EIS data nomencalture that was created. Master is made up of two subsets: loggers and meters. Loggers are sometimes called controllers and meters are sometimes called sensors. In rare cases meters or sensors are also called channels.


In [2]:
planon=pd.read_excel('EIS Assets.xlsx',index_col = 'Code')
master_loggerscontrollers = pd.read_csv('LoggersControllers.csv', index_col = 'Asset Code')
master_meterssensors = pd.read_csv('MetersSensors.csv', encoding = 'macroman', index_col = 'Asset Code')
planon['Code']=planon.index
master_loggerscontrollers['Code']=master_loggerscontrollers.index
master_meterssensors['Code']=master_meterssensors.index

Unify index, caps everything and strip of trailing spaces.


In [3]:
planon.index=[str(i).upper().strip() for i in planon.index]
master_loggerscontrollers.index=[str(i).upper().strip() for i in master_loggerscontrollers.index]
master_meterssensors.index=[str(i).upper().strip() for i in master_meterssensors.index]

Drop duplicates (shouldn't be any)


In [4]:
planon.drop_duplicates(inplace=True)
master_loggerscontrollers.drop_duplicates(inplace=True)
master_meterssensors.drop_duplicates(inplace=True)

Split Planon import into loggers and meters
Drop duplicates (shouldn't be any)


In [5]:
# Split the Planon file into 2, one for loggers & controllers, and one for meters & sensors.
planon_loggerscontrollers = planon.loc[(planon['Classification Group'] == 'EN.EN4 BMS Controller') | (planon['Classification Group'] == 'EN.EN1 Data Logger')]
planon_meterssensors = planon.loc[(planon['Classification Group'] == 'EN.EN2 Energy Meter') | (planon['Classification Group'] == 'EN.EN3 Energy Sensor')]
planon_loggerscontrollers.drop_duplicates(inplace=True)
planon_meterssensors.drop_duplicates(inplace=True)


C:\Anaconda2\envs\python3\lib\site-packages\pandas\util\decorators.py:91: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  return func(*args, **kwargs)

Index unique? show number of duplicates in index


In [6]:
len(planon_loggerscontrollers.index[planon_loggerscontrollers.index.duplicated()])


Out[6]:
0

In [7]:
len(planon_meterssensors.index[planon_meterssensors.index.duplicated()])


Out[7]:
3089

Meters are not unique. This is becasue of the spaces served. This is ok for now, we will deal with duplicates at the comparison stage. Same is true for loggers - in the unlikely event that there are duplicates in the future.


In [8]:
planon_meterssensors.head(3)


Out[8]:
BuildingNo. Building Locations.Space.Space number Space Name Additional Location Info Description Classification Group Record HVAC Ref Element Description ... Logger SIM Meter Pulse Value Meter Units Meter Capacity Network Point ID Tenant Meter.Name Fiscal Meter.Name EIS Space.Space number Utility Type.Name Code
AP000-L01/M001 AP000 Alexandra Park NaN NaN NaN NaN EN.EN2 Energy Meter NaN NaN NaN ... NaN NaN NaN NaN NaN No No NaN NaN AP000-L01/M001
AP000-L02/M001 AP000 Alexandra Park NaN NaN NaN Graduate House 1-11 EN.EN2 Energy Meter NaN NaN NaN ... NaN NaN NaN NaN NaN No No NaN NaN AP000-L02/M001
AP000-L02/M002 AP000 Alexandra Park NaN NaN NaN Alex Park Main EN.EN2 Energy Meter NaN NaN NaN ... NaN NaN NaN NaN NaN No No NaN NaN AP000-L02/M002

3 rows × 30 columns

2. Validation

Create list of all buildings present in Planon export. These are buildings to check the data against from Master.


In [9]:
buildings=set(planon_meterssensors['BuildingNo.'])
buildings


Out[9]:
{'AP000',
 'AP001',
 'AP009',
 'AP010',
 'AP011',
 'AP057',
 'AP080',
 'AP081',
 'EX001',
 'MC000',
 'MC001',
 'MC003',
 'MC007',
 'MC008',
 'MC010',
 'MC011',
 'MC013',
 'MC014',
 'MC029',
 'MC030',
 'MC031',
 'MC032',
 'MC033',
 'MC043',
 'MC044',
 'MC045',
 'MC046',
 'MC047',
 'MC048',
 'MC050',
 'MC051',
 'MC053',
 'MC055',
 'MC061',
 'MC062',
 'MC063',
 'MC064',
 'MC065',
 'MC066',
 'MC067',
 'MC068',
 'MC069',
 'MC070',
 'MC071',
 'MC072',
 'MC075',
 'MC076',
 'MC077',
 'MC078',
 'MC083',
 'MC095',
 'MC099',
 'MC102',
 'MC103',
 'MC125',
 'MC126',
 'MC128',
 'MC129',
 'MC131',
 'MC134',
 'MC138',
 'MC139',
 'MC140',
 'MC141',
 'MC171',
 'MC181',
 'MC197',
 'MC198',
 'MC199',
 'MC200',
 'MC202',
 'MC203',
 'MC204',
 'MC207',
 'MC208',
 'MC210',
 'MC211',
 'OC004',
 'OC005',
 'OC006'}

In [10]:
len(buildings)


Out[10]:
80

2.1. Meters

Create dataframe slice for validation from master_meterssensors where the only the buildings located in buildings are contained. Save this new slice into master_meterssensors_for_validation. This is done by creating sub-slices of the dataframe for each building, then concatenating them all together.


In [11]:
master_meterssensors_for_validation = \
    pd.concat([master_meterssensors.loc[master_meterssensors['Building Code'] == building] \
           for building in buildings])
master_meterssensors_for_validation.head(2)


Out[11]:
Logger Asset Code Description Make Model Meter Units Meter Pulse Value Classification Group Logger Channel Utility Type ?? ... Building Name Space Additional Location Info Tenant meter Fiscal meter Parent meter Child meters Communications type Electrical panel ID Code
MC139-L01/M001 050157AC6600 Sub No.9 Transformer No.1 Carlo Gavazzi EM24 kWh 1.0 Energy meter 1 Electricity NaN ... Sub Station 9 NaN Graduate Field 0.0 0.0 NaN NaN NaN NaN MC139-L01/M001
MC139-L01/M002 050157AC6600 Sub No.9 Transformer No.2 Carlo Gavazzi EM24 kWh 1.0 Energy meter 2 Electricity NaN ... Sub Station 9 NaN Graduate Field 0.0 0.0 NaN NaN NaN NaN MC139-L01/M002

2 rows × 22 columns


In [12]:
master_meterssensors_for_validation.loc['MC202-B15/F15']


Out[12]:
Logger Asset Code           {05937EE0-58E6-42F3-B6BD-A180D9634B6C}
Description                                            Function 15
Make                                                           NaN
Model                                                          NaN
Meter Units                                                    NaN
Meter Pulse Value                                              NaN
Classification Group                                 Energy sensor
Logger Channel                                                 F15
Utility Type                                                   NaN
??                                                             NaN
Meter Type                                                     NaN
Building Code                                                MC202
Building Name                              Charles Carter Building
Space                                                          NaN
Additional Location Info                                       NaN
Tenant meter                                                     0
Fiscal meter                                                     0
Parent meter                                                   NaN
Child meters                                                   NaN
Communications type                                            NaN
Electrical panel ID                                            NaN
Code                                                 MC202-B15/F15
Name: MC202-B15/F15, dtype: object

In [13]:
#alternative method
master_meterssensors_for_validation2 = \
    master_meterssensors[master_meterssensors['Building Code'].isin(buildings)]
master_meterssensors_for_validation2.head(2)


Out[13]:
Logger Asset Code Description Make Model Meter Units Meter Pulse Value Classification Group Logger Channel Utility Type ?? ... Building Name Space Additional Location Info Tenant meter Fiscal meter Parent meter Child meters Communications type Electrical panel ID Code
MC055-B01/C1 {6BF82177-CB8B-4774-9B9C-0008202C0CE8} Virtual CNC 1 NaN NaN NaN NaN Energy sensor C1 NaN NaN ... Furness Residences NaN NaN 0.0 0.0 NaN NaN NaN NaN MC055-B01/C1
MC055-B01/D1 {6BF82177-CB8B-4774-9B9C-0008202C0CE8} Heating Pumps 1 & 2 NaN NaN NaN NaN Energy sensor D1 NaN NaN ... Furness Residences NaN NaN 0.0 0.0 NaN NaN NaN NaN MC055-B01/D1

2 rows × 22 columns

Planon sensors are not unique because of the spaces served convention in the two data architectures. The Planon architecture devotes a new line for each space served - hence the not unique index. The Master architecture lists all the spaces only once, as a list, therefore it has a unique index. We will need to take this into account and create matching dataframe out of planon for comparison, with a unique index.


In [14]:
len(master_meterssensors_for_validation)


Out[14]:
30539

In [15]:
len(planon_meterssensors)-len(planon_meterssensors.index[planon_meterssensors.index.duplicated()])


Out[15]:
29623

Sort datasets after index for easier comparison.


In [16]:
master_meterssensors_for_validation.sort_index(inplace=True)
planon_meterssensors.sort_index(inplace=True)


C:\Anaconda2\envs\python3\lib\site-packages\ipykernel\__main__.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  from ipykernel import kernelapp as app

2.1.1 Slicing of meters to only certain columns of comparison


In [17]:
planon_meterssensors.T


Out[17]:
AP000-L01/M001 AP000-L02/M001 AP000-L02/M002 AP000-L03/M001 AP000-L03/M002 AP001-L01/M001 AP001-L01/M002 AP001-L01/M003 AP001-L01/M004 AP001-L01/M005 ... OC006-B01/W4 OC006-B01/W5 OC006-B01/Y1 OC006-B01/Y2 OC006-B01/Y3 OC006-B01/Y4 OC006-B01/Y5 OC006-B01/Y6 OC006-B01/Z1 OC006-B01/Z2
BuildingNo. AP000 AP000 AP000 AP000 AP000 AP001 AP001 AP001 AP001 AP001 ... OC006 OC006 OC006 OC006 OC006 OC006 OC006 OC006 OC006 OC006
Building Alexandra Park Alexandra Park Alexandra Park Alexandra Park Alexandra Park House 01 - Bassenthwaite, Graduate College House 01 - Bassenthwaite, Graduate College House 01 - Bassenthwaite, Graduate College House 01 - Bassenthwaite, Graduate College House 01 - Bassenthwaite, Graduate College ... Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House
Locations.Space.Space number NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Space Name NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Additional Location Info NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Description NaN Graduate House 1-11 Alex Park Main NaN NaN NaN Bar Cellar Bar Ground Services Room Bar Water ... ShuntPumpRotate Manual Shunt pump changeover Outside air temperature Immersion Sensor Thermistor TBTC Room temperature Thermistor TBTI 4DIX V Heating Boilers DHWS Immersion
Classification Group EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter EN.EN2 Energy Meter ... EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor EN.EN3 Energy Sensor
Record NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
HVAC Ref NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Element Description NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Servicable Area NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Model NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Make NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
EIS ID 1 1 2 1 2 1 2 3 4 5 ... W4 W5 Y1 Y2 Y3 Y4 Y5 Y6 Z1 Z2
Logger Channel 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
Logger Upstream Comms Target NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Logger Modem Serial Number NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Logger IP Address NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Logger Serial Number NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Logger MAC Address NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Logger SIM NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Meter Pulse Value NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Meter Units NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Meter Capacity NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Network Point ID NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Tenant Meter.Name No No No No No No No No No No ... No No No No No No No No No No
Fiscal Meter.Name No No No No No No No No No No ... No No No No No No No No No No
EIS Space.Space number NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Utility Type.Name NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Code AP000-L01/M001 AP000-L02/M001 AP000-L02/M002 AP000-L03/M001 AP000-L03/M002 AP001-L01/M001 AP001-L01/M002 AP001-L01/M003 AP001-L01/M004 AP001-L01/M005 ... OC006-B01/W4 OC006-B01/W5 OC006-B01/Y1 OC006-B01/Y2 OC006-B01/Y3 OC006-B01/Y4 OC006-B01/Y5 OC006-B01/Y6 OC006-B01/Z1 OC006-B01/Z2

30 rows × 32712 columns


In [18]:
master_meterssensors_for_validation.T


Out[18]:
AP000-L01/M001 AP000-L02/M001 AP000-L02/M002 AP000-L03/M001 AP000-L03/M002 AP000-L99/M303 AP000-L99/M308 AP001-L01/M001 AP001-L01/M002 AP001-L01/M003 ... OC006-B01/Y1 OC006-B01/Y2 OC006-B01/Y3 OC006-B01/Y4 OC006-B01/Y5 OC006-B01/Y6 OC006-B01/Z1 OC006-B01/Z2 OC006-L99/M200 OC006-L99/M503
Logger Asset Code 050157C7ED00 37475126 37475126 48015355 48015355 NaN NaN 0501E3E97100 0501E3E97100 0501E3E97100 ... {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} {BBD3685B-B0DC-417F-A0E8-20139B1074E1} NaN NaN
Description NaN Graduate House 1-11 Alex Park Main NaN NaN Alex Park LU Alex Park UPP NaN Bar Cellar Bar Ground ... Outside air temperature Immersion Sensor Thermistor TBTC Room temperature Thermistor TBTI 4DIX V Heating Boilers DHWS Immersion Kent House Kent House
Make Carlo Gavazzi Actaris Delta NaN NaN Elster Landis ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Model EM24 Delta G D35 NaN NaN A1100 E1100 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Meter Units kWh NaN NaN NaN NaN NaN NaN m3 kWh kWh ... Degrees Celsius NaN NaN Degrees Celsius Degrees Celsius NaN NaN NaN NaN NaN
Meter Pulse Value 1 NaN NaN NaN NaN NaN NaN 0.001 0.001 0.001 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Classification Group Energy meter Energy meter Energy meter Energy meter Energy meter Energy meter Energy meter Energy meter Energy meter Energy meter ... Energy sensor Energy sensor Energy sensor Energy sensor Energy sensor Energy sensor Energy sensor Energy sensor Energy meter Energy meter
Logger Channel 1 1 2 1 2 NaN NaN 1 2 3 ... Y1 Y2 Y3 Y4 Y5 Y6 Z1 Z2 NaN NaN
Utility Type Electricity Natural Gas Natural Gas Water Water Natural Gas Natural Gas Water Electricity Electricity ... NaN NaN NaN NaN NaN NaN NaN NaN Electricity Water
?? NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Meter Type NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Building Code AP000 AP000 AP000 AP000 AP000 AP000 AP000 AP001 AP001 AP001 ... OC006 OC006 OC006 OC006 OC006 OC006 OC006 OC006 OC006 OC006
Building Name Alexandra Park Alexandra Park Alexandra Park Alexandra Park Alexandra Park Alexandra Park Alexandra Park House 01 - Bassenthwaite House 01 - Bassenthwaite House 01 - Bassenthwaite ... Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House Chancellor's Wharf, Kent House
Space NaN NaN NaN NaN NaN AP001 A0, AP002 A0, AP003 A0, AP004 A0, Ap005 ... AP012 A0, AP013 A0, AP014 A0, AP015 A0, AP016 ... NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN OC006 A0 OC006 A0
Additional Location Info Graduate 1-11 Plant Room External Gas Meter Building External Gas Meter Building External Water Meter Chamber External Water Meter Chamber NaN NaN Bassenthwaite Rm No 104 Bassenthwaite Rm No 104 Bassenthwaite Rm No 104 ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Tenant meter 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
Fiscal meter 0 0 0 0 0 1 1 0 0 0 ... 0 0 0 0 0 0 0 0 1 1
Parent meter NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Child meters NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Communications type NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Electrical panel ID NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
Code AP000-L01/M001 AP000-L02/M001 AP000-L02/M002 AP000-L03/M001 AP000-L03/M002 AP000-L99/M303 AP000-L99/M308 AP001-L01/M001 AP001-L01/M002 AP001-L01/M003 ... OC006-B01/Y1 OC006-B01/Y2 OC006-B01/Y3 OC006-B01/Y4 OC006-B01/Y5 OC006-B01/Y6 OC006-B01/Z1 OC006-B01/Z2 OC006-L99/M200 OC006-L99/M503

22 rows × 30539 columns

Create dictionary that maps Planon column names onto Master.

From Nicola:

  • Code (Asset Code)
  • Description
  • EIS ID (Channel)
  • Utility Type
  • Fiscal Meter
  • Tenant Meter

Building code and Building name are implicitly included. Logger Serial Number, IP or MAC would be essential to include, as well as Make and Model. Additional Location Info is not essnetial but would be useful to have. Locations (Locations.Space.Space number and Space Name) are included in the Planon export - but this is their only viable data source, therefore are not validated against.


In [19]:
#Planon:Master
meters_match_dict={
      "BuildingNo.":"Building Code",
      "Building":"Building Name",
      "Description":"Description",
      "EIS ID":"Logger Channel",
      "Tenant Meter.Name":"Tenant meter",
      "Fiscal Meter.Name":"Fiscal meter",
      "Code":"Code"
      }

Filter both dataframes based on these new columns. Then remove duplicates. Currently, this leads to loss of information of spaces served, but also a unique index for the Planon dataframe, therefore bringing the dataframes closer to each other. When including spaces explicitly in the comparison (if we want to - or just trust the Planon space mapping), this needs to be modified.


In [20]:
master_meterssensors_for_validation_filtered=master_meterssensors_for_validation[list(meters_match_dict.values())]
planon_meterssensors_filtered=planon_meterssensors[list(meters_match_dict.keys())]

In [21]:
pd.DataFrame(master_meterssensors_for_validation_filtered.loc['MC202-B15/F15'])


Out[21]:
MC202-B15/F15
Code MC202-B15/F15
Fiscal meter 0
Description Function 15
Tenant meter 0
Building Name Charles Carter Building
Logger Channel F15
Building Code MC202

In [22]:
master_meterssensors_for_validation_filtered.head(2)


Out[22]:
Code Fiscal meter Description Tenant meter Building Name Logger Channel Building Code
AP000-L01/M001 AP000-L01/M001 0.0 NaN 0.0 Alexandra Park 1 AP000
AP000-L02/M001 AP000-L02/M001 0.0 Graduate House 1-11 0.0 Alexandra Park 1 AP000

In [23]:
planon_meterssensors_filtered.head(2)


Out[23]:
Code Fiscal Meter.Name Description Tenant Meter.Name Building EIS ID BuildingNo.
AP000-L01/M001 AP000-L01/M001 No NaN No Alexandra Park 1 AP000
AP000-L02/M001 AP000-L02/M001 No Graduate House 1-11 No Alexandra Park 1 AP000

Unify headers, drop duplicates (bear the mind the spaces argument, this where it needs to be brought back in in the future!).


In [24]:
planon_meterssensors_filtered.columns=[meters_match_dict[i] for i in planon_meterssensors_filtered]

In [25]:
planon_meterssensors_filtered.drop_duplicates(inplace=True)
master_meterssensors_for_validation_filtered.drop_duplicates(inplace=True)


C:\Anaconda2\envs\python3\lib\site-packages\pandas\util\decorators.py:91: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  return func(*args, **kwargs)

In [26]:
planon_meterssensors_filtered.head(2)


Out[26]:
Code Fiscal meter Description Tenant meter Building Name Logger Channel Building Code
AP000-L01/M001 AP000-L01/M001 No NaN No Alexandra Park 1 AP000
AP000-L02/M001 AP000-L02/M001 No Graduate House 1-11 No Alexandra Park 1 AP000

Fiscal/Tenant meter name needs fixing from Yes/No and 1/0.


In [27]:
planon_meterssensors_filtered['Fiscal meter']=planon_meterssensors_filtered['Fiscal meter'].isin(['Yes'])
planon_meterssensors_filtered['Tenant meter']=planon_meterssensors_filtered['Tenant meter'].isin(['Yes'])
master_meterssensors_for_validation_filtered['Fiscal meter']=master_meterssensors_for_validation_filtered['Fiscal meter'].isin([1])
master_meterssensors_for_validation_filtered['Tenant meter']=master_meterssensors_for_validation_filtered['Tenant meter'].isin([1])


C:\Anaconda2\envs\python3\lib\site-packages\ipykernel\__main__.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  if __name__ == '__main__':
C:\Anaconda2\envs\python3\lib\site-packages\ipykernel\__main__.py:2: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  from ipykernel import kernelapp as app
C:\Anaconda2\envs\python3\lib\site-packages\ipykernel\__main__.py:3: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  app.launch_new_instance()
C:\Anaconda2\envs\python3\lib\site-packages\ipykernel\__main__.py:4: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

In [28]:
master_meterssensors_for_validation_filtered.head(2)


Out[28]:
Code Fiscal meter Description Tenant meter Building Name Logger Channel Building Code
AP000-L01/M001 AP000-L01/M001 False NaN False Alexandra Park 1 AP000
AP000-L02/M001 AP000-L02/M001 False Graduate House 1-11 False Alexandra Park 1 AP000

In [29]:
planon_meterssensors_filtered.head(2)


Out[29]:
Code Fiscal meter Description Tenant meter Building Name Logger Channel Building Code
AP000-L01/M001 AP000-L01/M001 False NaN False Alexandra Park 1 AP000
AP000-L02/M001 AP000-L02/M001 False Graduate House 1-11 False Alexandra Park 1 AP000

Cross-check missing meters


In [30]:
a=np.sort(list(set(planon_meterssensors_filtered.index)))
b=np.sort(list(set(master_meterssensors_for_validation_filtered.index)))
meterssensors_not_in_planon=[]
for i in b:
    if i not in a:
        print(i+',',end=" "),
        meterssensors_not_in_planon.append(i)
print('\n\nMeters in Master, but not in Planon:',
      len(meterssensors_not_in_planon),'/',len(b),':',
      round(len(meterssensors_not_in_planon)/len(b)*100,3),'%')


AP000-L99/M303, AP000-L99/M308, MC000-L99/M201, MC000-L99/M202, MC000-L99/M203, MC000-L99/M506, MC001-L99/M100, MC001-L99/M222, MC001-L99/M224, MC001-L99/M306, MC001-L99/M508, MC003-L99/M207, MC003-L99/M304, MC003-L99/M509, MC007-L99/M211, MC007-L99/M511, MC008-L99/M302, MC047-L02/M00??, MC061-L99/M305, MC061-L99/M310, MC102-L99/M309, MC103-L99/M216, MC103-L99/M300, MC103-L99/M504, MC204-L99/M312, MC210-L01/M001, MC210-L01/M002, MC210-L01/M003, MC210-L01/M004, MC210-L01/M005, MC210-L01/M006, MC210-L01/M007, MC210-L01/M008, MC210-L01/M009, MC210-L01/M010, MC210-L01/M011, MC210-L01/M012, MC210-L01/M013, MC210-L01/M014, MC210-L01/M015, MC210-L01/M016, MC210-L01/M017, MC210-L01/M018, MC210-L01/M019, MC210-L01/M020, MC210-L01/M021, MC210-L01/M022, MC210-L01/M023, MC210-L01/M024, MC210-L01/M025, MC210-L01/M026, MC210-L01/M027, MC210-L01/M028, MC210-L01/M029, MC210-L01/M030, MC210-L02/M001, MC210-L02/M002, MC210-L02/M003, MC210-L02/M004, MC210-L02/M005, MC210-L02/M006, MC210-L02/M007, MC210-L02/M008, MC210-L02/M009, MC210-L02/M010, MC210-L02/M011, MC210-L02/M012, MC210-L02/M013, MC210-L02/M014, MC210-L02/M015, MC210-L02/M016, MC210-L02/M017, MC210-L02/M018, MC210-L02/M019, MC210-L02/M020, MC210-L02/M021, MC210-L02/M022, MC210-L02/M023, NAN, OC004-L99/M206, OC004-L99/M512, OC005-L99/M204, OC005-L99/M505, OC006-L99/M200, OC006-L99/M503, 

Meters in Master, but not in Planon: 85 / 29708 : 0.286 %

In [31]:
#without MC210
len(set([i for i in meterssensors_not_in_planon if i[:5]!='MC210']))


Out[31]:
32

In [32]:
set([i for i in meterssensors_not_in_planon if i[:5]!='MC210'])


Out[32]:
{'AP000-L99/M303',
 'AP000-L99/M308',
 'MC000-L99/M201',
 'MC000-L99/M202',
 'MC000-L99/M203',
 'MC000-L99/M506',
 'MC001-L99/M100',
 'MC001-L99/M222',
 'MC001-L99/M224',
 'MC001-L99/M306',
 'MC001-L99/M508',
 'MC003-L99/M207',
 'MC003-L99/M304',
 'MC003-L99/M509',
 'MC007-L99/M211',
 'MC007-L99/M511',
 'MC008-L99/M302',
 'MC047-L02/M00??',
 'MC061-L99/M305',
 'MC061-L99/M310',
 'MC102-L99/M309',
 'MC103-L99/M216',
 'MC103-L99/M300',
 'MC103-L99/M504',
 'MC204-L99/M312',
 'NAN',
 'OC004-L99/M206',
 'OC004-L99/M512',
 'OC005-L99/M204',
 'OC005-L99/M505',
 'OC006-L99/M200',
 'OC006-L99/M503'}

In [ ]:
(set([i[:5] for i in meterssensors_not_in_planon]))


Out[ ]:
{'AP000',
 'MC000',
 'MC001',
 'MC003',
 'MC007',
 'MC008',
 'MC047',
 'MC061',
 'MC102',
 'MC103',
 'MC204',
 'MC210',
 'NAN',
 'OC004',
 'OC005',
 'OC006'}

In [ ]:
a=np.sort(list(set(planon_meterssensors_filtered.index)))
b=np.sort(list(set(master_meterssensors_for_validation_filtered.index)))
meterssensors_not_in_master=[]
for i in a:
    if i not in b:
        print(i+',',end=" "),
        meterssensors_not_in_master.append(i)
print('\n\nMeters in Planon, not in Master:',
      len(meterssensors_not_in_master),'/',len(a),':',
      round(len(meterssensors_not_in_master)/len(a)*100,3),'%')

In [ ]:
len(set([i for i in meterssensors_not_in_master]))

In [ ]:
set([i[:9] for i in meterssensors_not_in_master])

In [ ]:
set([i[:5] for i in meterssensors_not_in_master])

Check for duplicates in index, but not duplicates over the entire row


In [ ]:
print(len(planon_meterssensors_filtered.index))
print(len(set(planon_meterssensors_filtered.index)))
print(len(master_meterssensors_for_validation_filtered.index))
print(len(set(master_meterssensors_for_validation_filtered.index)))

In [ ]:
master_meterssensors_for_validation_filtered[master_meterssensors_for_validation_filtered.index.duplicated()]

The duplicates are the nans. Remove these for now. Could revisit later to do an index-less comparison, only over row contents.


In [ ]:
good_index=[i for i in master_meterssensors_for_validation_filtered.index if str(i).lower().strip()!='nan']
master_meterssensors_for_validation_filtered=master_meterssensors_for_validation_filtered.loc[good_index]
master_meterssensors_for_validation_filtered.drop_duplicates(inplace=True)

In [ ]:
len(planon_meterssensors_filtered)

In [ ]:
len(master_meterssensors_for_validation_filtered)

Do comparison only on common indices. Need to revisit and identify the cause missing meters, both ways (5 Planon->Meters and 30 Meters->Planon in this example).


In [ ]:
comon_index=list(set(master_meterssensors_for_validation_filtered.index).intersection(set(planon_meterssensors_filtered.index)))

In [ ]:
len(comon_index)

In [ ]:
master_meterssensors_for_validation_intersected=master_meterssensors_for_validation_filtered.loc[comon_index].sort_index()
planon_meterssensors_intersected=planon_meterssensors_filtered.loc[comon_index].sort_index()

In [ ]:
len(master_meterssensors_for_validation_intersected)

In [ ]:
len(planon_meterssensors_intersected)

Still have duplicate indices. For now we just drop and keep the first.


In [ ]:
master_meterssensors_for_validation_intersected = master_meterssensors_for_validation_intersected[~master_meterssensors_for_validation_intersected.index.duplicated(keep='first')]

In [ ]:
master_meterssensors_for_validation_intersected.head(2)

In [ ]:
planon_meterssensors_intersected.head(2)

2.1.2. Primitive comparison


In [ ]:
planon_meterssensors_intersected==master_meterssensors_for_validation_intersected

In [ ]:
np.all(planon_meterssensors_intersected==master_meterssensors_for_validation_intersected)

2.1.3. Horizontal comparison

Number of cells matching


In [ ]:
(planon_meterssensors_intersected==master_meterssensors_for_validation_intersected).sum()

Percentage matching


In [ ]:
(planon_meterssensors_intersected==master_meterssensors_for_validation_intersected).sum()/\
len(planon_meterssensors_intersected)*100

In [ ]:
((planon_meterssensors_intersected==master_meterssensors_for_validation_intersected).sum()/\
len(planon_meterssensors_intersected)*100).plot(kind='bar')

2.1.4. Vertical comparison


In [ ]:
df=pd.DataFrame((planon_meterssensors_intersected.T==master_meterssensors_for_validation_intersected.T).sum())
df

In [ ]:
df=pd.DataFrame((planon_meterssensors_intersected.T==master_meterssensors_for_validation_intersected.T).sum()/\
len(planon_meterssensors_intersected.T)*100)
df[df[0]<100]

2.1.5. Smart(er) comparison

Not all of the dataframe matches. Let us do some basic string formatting, maybe that helps.


In [ ]:
sum(planon_meterssensors_intersected['Description']!=master_meterssensors_for_validation_intersected['Description'])

In [ ]:
planon_meterssensors_intersected['Description']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ') for s in planon_meterssensors_intersected['Description'].values]
master_meterssensors_for_validation_intersected['Description']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ') for s in master_meterssensors_for_validation_intersected['Description'].values]

In [ ]:
sum(planon_meterssensors_intersected['Description']!=master_meterssensors_for_validation_intersected['Description'])

Some errors fixed, some left. Let's see which ones. These are either:

  • Wrong duplicate dropped
  • Input human erros in the description.
  • Actual erros somewhere in the indexing.

In [ ]:
for i in planon_meterssensors_intersected[planon_meterssensors_intersected['Description']!=master_meterssensors_for_validation_intersected['Description']].index:
    print(i,'\t\tPlanon:',planon_meterssensors_intersected.loc[i]['Description'],'\t\tMaster:',master_meterssensors_for_validation_intersected.loc[i]['Description'])

Let us repeat the exercise for Logger Channel. Cross-validate, flag as highly likely error where both mismatch.


In [ ]:
sum(planon_meterssensors_intersected['Logger Channel']!=master_meterssensors_for_validation_intersected['Logger Channel'])

In [ ]:
planon_meterssensors_intersected['Logger Channel']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ') for s in planon_meterssensors_intersected['Logger Channel'].values]
master_meterssensors_for_validation_intersected['Logger Channel']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ') for s in master_meterssensors_for_validation_intersected['Logger Channel'].values]

In [ ]:
sum(planon_meterssensors_intersected['Logger Channel']!=master_meterssensors_for_validation_intersected['Logger Channel'])

All errors fixed on logger channels.


In [ ]:
for i in planon_meterssensors_intersected[planon_meterssensors_intersected['Logger Channel']!=master_meterssensors_for_validation_intersected['Logger Channel']].index:
    print(i,'\t\tPlanon:',planon_meterssensors_intersected.loc[i]['Logger Channel'],'\t\tMaster:',master_meterssensors_for_validation_intersected.loc[i]['Logger Channel'])

New error percentage:


In [ ]:
(planon_meterssensors_intersected!=master_meterssensors_for_validation_intersected).sum()/\
len(planon_meterssensors_intersected)*100

2.2. Loggers


In [ ]:
buildings=set(planon_loggerscontrollers['BuildingNo.'])
buildings

In [ ]:
master_loggerscontrollers_for_validation = \
    pd.concat([master_loggerscontrollers.loc[master_loggerscontrollers['Building Code'] == building] \
           for building in buildings])
master_loggerscontrollers_for_validation.head(2)

In [ ]:
master_loggerscontrollers[master_loggerscontrollers['Building Code']=='MC060']

In [ ]:
planon_loggerscontrollers[planon_loggerscontrollers['BuildingNo.']=='MC060']

In [ ]:
master_loggerscontrollers.loc['MC060-L01']

In [ ]:
len(master_loggerscontrollers_for_validation)

In [ ]:
len(planon_loggerscontrollers)-len(planon_loggerscontrollers.index[planon_loggerscontrollers.index.duplicated()])

In [ ]:
master_loggerscontrollers_for_validation.sort_index(inplace=True)
planon_loggerscontrollers.sort_index(inplace=True)

In [ ]:
master_loggerscontrollers_for_validation.loc['MC060-L01']

In [ ]:
planon_loggerscontrollers.T

In [ ]:
master_loggerscontrollers_for_validation.T

Create dictionary that maps Planon column names onto Master.

From Nicola:

  • EIS ID (Serial Number)
  • Make
  • Model
  • Description
  • Code (Asset Code)
  • Building Code

Building code and Building name are implicitly included. Logger IP or MAC would be essential to include, as well as Make and Model. Additional Location Info is not essnetial but would be useful to have. Locations (Locations.Space.Space number and Space Name) are included in the Planon export - but this is their only viable data source, therefore are not validated against.


In [ ]:
#Planon:Master
loggers_match_dict={
      "BuildingNo.":"Building Code",
      "Building":"Building Name",
      "Description":"Description",
      "EIS ID":"Logger Serial Number",
      "Make":"Make",
      "Model":"Model",
      "Code":"Code"
      }

In [ ]:
master_loggerscontrollers_for_validation_filtered=master_loggerscontrollers_for_validation[list(loggers_match_dict.values())]
planon_loggerscontrollers_filtered=planon_loggerscontrollers[list(loggers_match_dict.keys())]

In [ ]:
master_loggerscontrollers_for_validation_filtered.head(2)

In [ ]:
planon_loggerscontrollers_filtered.head(2)

In [ ]:
planon_loggerscontrollers_filtered.columns=[loggers_match_dict[i] for i in planon_loggerscontrollers_filtered]

In [ ]:
planon_loggerscontrollers_filtered.drop_duplicates(inplace=True)
master_loggerscontrollers_for_validation_filtered.drop_duplicates(inplace=True)

In [ ]:
planon_loggerscontrollers_filtered.head(2)

In [ ]:
master_loggerscontrollers_for_validation_filtered.head(2)

In [ ]:
a=np.sort(list(set(planon_loggerscontrollers_filtered.index)))
b=np.sort(list(set(master_loggerscontrollers_for_validation_filtered.index)))
loggerscontrollers_not_in_planon=[]
for i in b:
    if i not in a:
        print(i+',',end=" "),
        loggerscontrollers_not_in_planon.append(i)
print('\n\nLoggers in Master, but not in Planon:',
      len(loggerscontrollers_not_in_planon),'/',len(b),':',
      round(len(loggerscontrollers_not_in_planon)/len(b)*100,3),'%')

In [ ]:
a=np.sort(list(set(planon_loggerscontrollers_filtered.index)))
b=np.sort(list(set(master_loggerscontrollers_for_validation_filtered.index)))
loggerscontrollers_not_in_master=[]
for i in a:
    if i not in b:
        print(i+',',end=" "),
        loggerscontrollers_not_in_master.append(i)
print('\n\nLoggers in Planon, not in Master:',
      len(loggerscontrollers_not_in_master),'/',len(a),':',
      round(len(loggerscontrollers_not_in_master)/len(a)*100,3),'%')

In [ ]:
print(len(planon_loggerscontrollers_filtered.index))
print(len(set(planon_loggerscontrollers_filtered.index)))
print(len(master_loggerscontrollers_for_validation_filtered.index))
print(len(set(master_loggerscontrollers_for_validation_filtered.index)))

In [ ]:
master_loggerscontrollers_for_validation_filtered[master_loggerscontrollers_for_validation_filtered.index.duplicated()]

In [ ]:
comon_index=list(set(master_loggerscontrollers_for_validation_filtered.index).intersection(set(planon_loggerscontrollers_filtered.index)))

In [ ]:
master_loggerscontrollers_for_validation_intersected=master_loggerscontrollers_for_validation_filtered.loc[comon_index].sort_index()
planon_loggerscontrollers_intersected=planon_loggerscontrollers_filtered.loc[comon_index].sort_index()

In [ ]:
master_loggerscontrollers_for_validation_intersected.head(2)

In [ ]:
planon_loggerscontrollers_intersected.head(2)

In [ ]:
planon_loggerscontrollers_intersected==master_loggerscontrollers_for_validation_intersected

Loggers matching


In [ ]:
(planon_loggerscontrollers_intersected==master_loggerscontrollers_for_validation_intersected).sum()

Percentage matching


In [ ]:
(planon_loggerscontrollers_intersected==master_loggerscontrollers_for_validation_intersected).sum()/\
len(planon_loggerscontrollers_intersected)*100

In [ ]:
((planon_loggerscontrollers_intersected==master_loggerscontrollers_for_validation_intersected).sum()/\
len(planon_loggerscontrollers_intersected)*100).plot(kind='bar')

Loggers not matching on Building Name.


In [ ]:
sum(planon_loggerscontrollers_intersected['Building Name']!=master_loggerscontrollers_for_validation_intersected['Building Name'])

In [ ]:
planon_loggerscontrollers_intersected['Building Name']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ') for s in planon_loggerscontrollers_intersected['Building Name'].values]
master_loggerscontrollers_for_validation_intersected['Building Name']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ') for s in master_loggerscontrollers_for_validation_intersected['Building Name'].values]

In [ ]:
sum(planon_loggerscontrollers_intersected['Building Name']!=master_loggerscontrollers_for_validation_intersected['Building Name'])

That didnt help.


In [ ]:
for i in planon_loggerscontrollers_intersected[planon_loggerscontrollers_intersected['Building Name']!=master_loggerscontrollers_for_validation_intersected['Building Name']].index:
    print(i,'\t\tPlanon:',planon_loggerscontrollers_intersected.loc[i]['Building Name'],'\t\tMaster:',master_loggerscontrollers_for_validation_intersected.loc[i]['Building Name'])

Follow up with lexical distance comparison. That would flag this as a match.

Loggers not matching on Serial Number.


In [ ]:
sum(planon_loggerscontrollers_intersected['Logger Serial Number']!=master_loggerscontrollers_for_validation_intersected['Logger Serial Number'])

In [ ]:
planon_loggerscontrollers_intersected['Logger Serial Number']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ').replace('{','').replace('}','') for s in planon_loggerscontrollers_intersected['Logger Serial Number'].values]
master_loggerscontrollers_for_validation_intersected['Logger Serial Number']=[str(s).lower().strip().replace('  ',' ').replace('   ',' ').replace('{','').replace('}','') for s in master_loggerscontrollers_for_validation_intersected['Logger Serial Number'].values]

In [ ]:
sum(planon_loggerscontrollers_intersected['Logger Serial Number']!=master_loggerscontrollers_for_validation_intersected['Logger Serial Number'])

In [ ]:
for i in planon_loggerscontrollers_intersected[planon_loggerscontrollers_intersected['Logger Serial Number']!=master_loggerscontrollers_for_validation_intersected['Logger Serial Number']].index:
    print(i,'\t\tPlanon:',planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number'],'\t\tMaster:',master_loggerscontrollers_for_validation_intersected.loc[i]['Logger Serial Number'])

Technically the same, but there is a number format error. Compare based on float value, if they match, replace one of them. This needs to be amended, as it will throw cannot onvert to float exception if strings are left in from the previous step.


In [ ]:
z1=[]
z2=[]    
for i in planon_loggerscontrollers_intersected.index:
    if planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number']!=master_loggerscontrollers_for_validation_intersected.loc[i]['Logger Serial Number']:
        if float(planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number'])==\
          float(master_loggerscontrollers_for_validation_intersected.loc[i]['Logger Serial Number']):
                z1.append(str(int(float(planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number']))))
                z2.append(str(int(float(planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number']))))
        else:
            z1.append(planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number'])
            z2.append(master_loggerscontrollers_for_validation_intersected.loc[i]['Logger Serial Number'])
    else:
        z1.append(planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number'])
        z2.append(planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number'])
planon_loggerscontrollers_intersected['Logger Serial Number']=z1
master_loggerscontrollers_for_validation_intersected['Logger Serial Number']=z2

In [ ]:
for i in planon_loggerscontrollers_intersected[planon_loggerscontrollers_intersected['Logger Serial Number']!=master_loggerscontrollers_for_validation_intersected['Logger Serial Number']].index:
    print(i,'\t\tPlanon:',planon_loggerscontrollers_intersected.loc[i]['Logger Serial Number'],'\t\tMaster:',master_loggerscontrollers_for_validation_intersected.loc[i]['Logger Serial Number'])

New error percentage:


In [ ]:
(planon_loggerscontrollers_intersected!=master_loggerscontrollers_for_validation_intersected).sum()/\
len(planon_loggerscontrollers_intersected)*100

(Bearing in my mind the above, this is technically 0)


In [ ]: