<jupyter_start><jupyter_text>Data sourceThe common data elements for predictive maintenance problems can be summarized as follows:* Machine features: The features specific to each individual machine, e.g. engine size, make, model, location, installation date.* Telemetry data: The operating condition data collected from sensors, e.g. temperature, vibration, operating speeds, pressures.* Maintenance history: The repair history of a machine, e.g. maintenance activities or component replacements, this can also include error code or runtime message logs.* Failure history: The failure history of a machine or component of interest.It is possible that failure history is contained within maintenance history, either as in the form of special error codes or order dates for spare parts. In those cases, failures can be extracted from the maintenance data. Additionally, different business domains may have a variety of other data sources that influence failure patterns which are not listed here exhaustively. These should be identified by consulting the domain experts when building predictive models.Some examples of above data elements from use cases are:    **Machine conditions and usage:** Flight routes and times, sensor data collected from aircraft engines, sensor readings from ATM transactions, train events data, sensor readings from wind turbines, elevators and connected cars.    **Machine features:** Circuit breaker technical specifications such as voltage levels, geolocation or car features such as make, model, engine size, tire types, production facility etc.**Failure history:** fight delay dates, aircraft component failure dates and types, ATM cash withdrawal transaction failures, train/elevator door failures, brake disk replacement order dates, wind turbine failure dates and circuit breaker command failures.**Maintenance history:** Flight error logs, ATM transaction error logs, train maintenance records including maintenance type, short description etc. and circuit breaker maintenance records.Given the above data sources, the two main data types we observe in predictive maintenance domain are temporal data and static data. Failure history, machine conditions, repair history, usage history are time series indicated by the timestamp of data collection. Machine and operator specific features, are more static, since they usually describe the technical specifications of machines or operator’s properties. Step 1: Data IngestionThis data aquisiton notebook will download the simulated predicitive maintenance data sets from blob storage. We do some preliminary data cleaning and verification, and store the results as a Spark data frame in cluster for use in the remaining notebook steps of this analysis.**Note:** This notebook will take less than 3 minutes to execute all cells on a Azure Databricks cluster of type Standard_DS13_v2 cluster, This include the time spent handling the _telemetry_ data set, which contains about 8.7 million records.<jupyter_code>import os
import time
import matplotlib.pyplot as plt

import seaborn as sns
import pandas as pd
plt.style.use('ggplot')

# Time the notebook execution. 
# This will only make sense if you "Run all cells"

tic = time.time()

parquet_files_names = {'machines':'machines_files.parquet','maint':'maint_files.parquet',
         'errors': 'errors_files.parquet','telemetry':'telemetry_files.parquet',
        'failures':'failure_files.parquet'}

csv_files_names = {'machines':'machines.csv','maint':'maint.csv',
         'errors': 'errors.csv','telemetry':'telemetry.csv',
        'failures':'failures.csv'}

target_dir = "dbfs:/dataset/"
storage_path = "wasb://predmaintenance@amlgitsamples.blob.core.windows.net/data/"

#dbutils.fs.rm(target_dir, recurse = True)
dbutils.fs.mkdirs(target_dir) 
dbutils.fs.cp(storage_path, target_dir, recurse=True)
display(dbutils.fs.ls(target_dir))<jupyter_output><empty_output><jupyter_text>Download simulated data setsThe five data files are: * machines.csv * maint.csv * errors.csv * telemetry.csv * failures.csvTo get an idea of what is contained in the data, we examine this machine schematic. ![Machine schematic](https://raw.githubusercontent.com/Azure/MachineLearningSamples-PredictiveMaintenance/master/images/machine.png)There are 1000 machines of four different models. Each machine contains four components of interest, and four sensors measuring voltage, pressure, vibration and rotation. A controller monitors the system and raises alerts for five different error conditions. Maintenance logs indicate when something is done to the machine which does not include a component replacement. A failure is defined by the replacement of a component. This notebook does some preliminary data cleanup, creates summary graphics for each data set to verify the data downloaded correctly, and stores the resulting data sets in the cluster storage.  Machines data setThis simulation tracks a simulated set of 1000 machines over the course of a single year (2015). This data set includes information about each machine: Machine ID, model type and age (years in service).<jupyter_code>machines =  spark.read.format("csv") \
  .option("header", "true") \
  .option("inferSchema", "true") \
  .load(os.path.join(storage_path,csv_files_names['machines']))
display(machines.take(5))<jupyter_output><empty_output><jupyter_text>The figure shows how long the collection of machines have been in service. It indicates there are four model types, shown in different colors, and all four models have been in service over the entire 20 years of service. The machine age will be a feature in our analysis, since we expect older machines may have a different set of errors and failures then machines that have not been in service long.<jupyter_code>machines_df = machines.toPandas()
plt.figure(figsize=(14, 7))
ax = sns.countplot(x="age", hue="model", data=machines_df,
                   order = machines_df.age.value_counts().index).set_title("Machines age by model")
del machines_df
display(ax.figure)<jupyter_output><empty_output><jupyter_text>Errors  data setThe error log contains non-breaking errors recorded while the machine is still operational. These errors are not considered failures, though they may be predictive of a future failure event. The error datetime field is rounded to the closest hour since the telemetry data (loaded later) is collected on an hourly rate.<jupyter_code>errors =  spark.read.format("csv") \
  .option("header", "true") \
  .option("inferSchema", "true") \
  .load(os.path.join(storage_path,csv_files_names['errors']))
display(errors.take(5))<jupyter_output><empty_output><jupyter_text>The figure shows how many errors occured in each of the five error classes over the entire year. We could split this figure over each individual machine, but with 1000 individuals, the figure would not be very informative.<jupyter_code>fig, ax = plt.subplots(figsize=(14,7)) 
errors_count = (spark.createDataFrame(errors.groupBy('errorID').count().collect())
              .toPandas())
sns.barplot(errors_count['errorID'], errors_count['count'], alpha=0.8).set_title('Errors distribution')
ax.set_ylabel("Count")
ax.set_xlabel("Error ID")
display(ax.figure)<jupyter_output><empty_output><jupyter_text>Maintenance data setThe maintenance log contains both scheduled and unscheduled maintenance records. Scheduled maintenance corresponds with  regular inspection of components, unscheduled maintenance may arise from mechanical failure or other performance degradations. A failure record is generated for component replacement in the case  of either maintenance events. Because maintenance events can also be used to infer component life, the maintenance data has been collected over two years (2014, 2015) instead of only over the year of interest (2015).<jupyter_code>maint=  spark.read.format("csv") \
  .option("header", "true") \
  .option("inferSchema", "true") \
  .load(os.path.join(storage_path,csv_files_names['maint']))
display(maint.take(5))<jupyter_output><empty_output><jupyter_text>The figure shows a histogram of component replacements divided into the four component types over the entire maintenance history. It looks like these four components are replaced at similar rates.There are many ways we might want to look at this data including calculating how long each component type lasts, or the time history of component replacements within each machine. This will take some preprocess of the data, which we are delaying until we do the feature engineering steps in the next example notebook.<jupyter_code>fig, ax = plt.subplots() 
components_count = (spark.createDataFrame(maint.groupBy('comp').count().collect())
              .toPandas())
sns.barplot(components_count['comp'], components_count['count'], alpha=0.8).set_title('Components distribution')
ax.set_ylabel("Count")
ax.set_xlabel("Component ID")
display(ax.figure)<jupyter_output><empty_output><jupyter_text>Telemetry data setThe telemetry time-series data consists of voltage, rotation, pressure, and vibration sensor measurements collected from each  machines in real time. The data is averaged over an hour and stored in the telemetry logs.<jupyter_code>telemetry =  spark.read.format("csv") \
  .option("header", "true") \
  .option("inferSchema", "true") \
  .load(os.path.join(storage_path, csv_files_names['telemetry']))

# handle missing values
# define groups of features 
telemetry_cols = telemetry.columns

features_datetime = ['datetime']
features_categorical = ['machineID']
features_numeric = list(set(telemetry_cols) - set(features_datetime) - set(features_categorical))

# Replace numeric NA with 0
telemetry = telemetry.fillna(0, subset = features_numeric)

# Replace categorical NA with 'Unknown'
telemetry = telemetry.fillna("Unknown", subset = features_categorical)

# Counts...
print(telemetry.count())

# Examine 10 rows of data.
display(telemetry.take(10))<jupyter_output><empty_output><jupyter_text>Rather than plot 8.7 million data points, The figure shows one month worth of telemetry sensor data for one machine.<jupyter_code>plt_data = telemetry.filter(telemetry.machineID == 1).toPandas()

# format datetime field which comes in as string
plt_data['datetime'] = pd.to_datetime(plt_data['datetime'], format="%Y-%m-%d %H:%M:%S")


# Quick plot to show structure
plot_df = plt_data.loc[(plt_data['datetime'] >= pd.to_datetime('2015-02-01')) &
                       (plt_data['datetime'] <= pd.to_datetime('2015-03-01'))]

plt_data = pd.melt(plot_df, id_vars=['datetime', 'machineID'])

fig, ax = plt.subplots(figsize=(14,7))
plt_data.groupby(['datetime','variable']).mean()['value']\
    .unstack().plot(ax=ax, title = 'Telemetry sensors measurements of one month')
ax.legend(loc='center', bbox_to_anchor=(0.5, -0.05),ncol= 4,
         fancybox=True, shadow=True)
display(fig)<jupyter_output><empty_output><jupyter_text>Failures data setFailures correspond to component replacements within the maintenance log. Each record contains the Machine ID, component type, and replacement datetime. These records will be used to create the machine learning labels we will be trying to predict.<jupyter_code>failures =  spark.read.format("csv") \
  .option("header", "true") \
  .option("inferSchema", "true") \
  .load(os.path.join(storage_path,csv_files_names['failures']))
display(failures.take(5))<jupyter_output><empty_output><jupyter_text>d The following figure shows failure related replacements occured for each of the 4 component types over the entire year. of the failure records obtained from failure log. This log was built originally from component replacements the maintenance log file.<jupyter_code>fig, ax = plt.subplots(figsize=(14,7)) 
failures_count = (spark.createDataFrame(failures.groupBy('failure').count().collect())
              .toPandas())
sns.barplot(failures_count['failure'], failures_count['count'], alpha=0.8).set_title('Failures distribution by component')
ax.set_ylabel("Count")
ax.set_xlabel("Component ID")
display(ax.figure)<jupyter_output><empty_output><jupyter_text>Now we write the spark dataframes to disk as parquet files.<jupyter_code>machines.write.mode('overwrite').parquet(os.path.join(target_dir,parquet_files_names['machines']))
errors.write.mode('overwrite').parquet(os.path.join(target_dir,parquet_files_names['errors']))
maint.write.mode('overwrite').parquet(os.path.join(target_dir,parquet_files_names['maint']))
telemetry.write.mode('overwrite').parquet(os.path.join(target_dir,parquet_files_names['telemetry']))
failures.write.mode('overwrite').parquet(os.path.join(target_dir,parquet_files_names['failures']))

for key, val in csv_files_names.items():
  dbutils.fs.rm(os.path.join(target_dir, csv_files_names[key]))

toc = time.time()
print("Full run took %.2f minutes" % ((toc - tic)/60))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0933791380387258, 'fraction_numerical': 0.007339163023110556, 'mean_word_length': 5.946312364425163, 'pattern_counts': {'":': 0, '<': 50, '<?xml version=': 0, '>': 50, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '303668', 'license': 'MIT', 'n_tokens_mistral': 3539, 'n_tokens_neox': 3358, 'n_words': 1286, 'path': 'examples/predictive_maintenance/1_data_ingestion.ipynb', 'repo_name': 'kunZooberg/MLOps', 'size': '12808'}