Simple Automatic Stratigraphic Well Corellation with Python
If you ever do well corellation, you might find that corellation is boring and take so much time, especially if you have thousand of wells. I would like to share a simple way to do automatic Stratigraphic Well Corellation using Python.
So here’s the four simple steps on how to do stratigraphic well corelation automatically:
I collected the well log data from: https://maps.kgs.ku.edu/oilgas/ . The well that i used in this tutorial is Hudson 24, and Hudson 26. You might download the file here.
1. Data loading, Visualization, and Preprocessing
The very first step is loading all required packages, I use as minimal package as possible, but i found that Las package is very helpful in dealing with Well log data. Using LASReader().data function on las we will be able to read las data table directly from las file.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import las
marker = pd.read_csv(‘data/marker_24.csv’,sep=’;’)well1 = las.LASReader(‘data/Hamilton 24.las’).data
well2 = las.LASReader(‘data/Hamilton 26.las’).data
Note that the well data will loaded as an inxed array very neatly. However, we wanted it to be in Pandas Dataframe format to make data slicing and indexing much easier. Don’t forget to put .replace(-999.25, np.nan) to replace default Las data for null into Numpy null in Dataframe.
well1 = pd.DataFrame(well1).replace(-999.25, np.nan)
well2 = pd.DataFrame(well2).replace(-999.25, np.nan)
well1.head()
This what the data will be looks like as DataFrame table, note that it consist only GR and NEU logs. Thus we need to focus on using GR log for corelation.
Data Checking
It is necessary step, we need to see whether the GR log have different distribution.
plt.hist(well1.GR, bins=np.linspace(0,150,25), alpha=0.5, label = 'Hamilton 24')
plt.hist(well2.GR, bins=np.linspace(0,150,25), alpha=0.5, label = 'Hamilton 26')
plt.legend()
We reckon both well have different Distribution, thus we need to normalize the data and store the normalized data on ‘GR_nor’
well1[‘GR_nor’] = (well1.GR — np.min(well1.GR)) / (np.max(well1.GR) — np.min(well1.GR))
well2[‘GR_nor’] = (well2.GR — np.min(well2.GR)) / (np.max(well2.GR) — np.min(well2.GR))
Now you have data that have similar distribution. The data now ready for processing.
Well Data Visualization
This step consist of well data visualization along with Marker on Hamilton 24 well using Matplotlib Plot module.
plt.figure(figsize=(6,10))plt.subplot(121)
plt.title('Hamilton 24')
plt.plot('GR_nor', 'DEPT', data=well1)
for i in marker.values:
plt.hlines(i[1], 0, 1,'red',linestyles='dashed', alpha=0.5)
plt.text(0, i[1],i[0])
plt.gca().invert_yaxis()plt.subplot(122)
plt.title('Hamilton 26')
plt.plot('GR_nor', 'DEPT', data=well2)plt.gca().invert_yaxis()
2. Search Template
In order to propagate the well marker, we need to select which marker that are very distinct and have unique propery. I chose “Hutchinson Salt Member” in Hudson 24 well because it marked by abrupt change of GR_nor value.
marker_depth = marker[marker.Formation == ‘Hutchinson Salt Member’].Top.values[0]
Search template is GR_nor value window that includes GR_nor values above and below the marker depth. In this case we would like to select the window size of 60ft (30ft for each above and below marker depth).
search_template = well1[(well1.DEPT>=marker_depth — 30) &(well1.DEPT<marker_depth + 30)].GR_nor.valuesprint(len(search_template))
The length of search_template is 120, thus we need to set the matching window to 120.
3. Matching and Scoring
Matching is a process that measure correlation coefficient between search_template with the data in Hudson 26 with row by row manner.
score_index = np.array([])
score = np.array([])
for i in range(60, len(well2)-60):
search_template_well2 = well2.GR_nor[i-60:i+60].values
corr = np.corrcoef(search_template, search_template_well2)[0,1]
score = np.append(score,corr)
score_index = np.append(score_index,i)
I used simple ‘for’ function to iterate over the target row, and store the score for each iteration in ‘score’ array.
I do also store the row index of each iteration in ‘score_index’ for ease in knowing the marker depth on target well later.
The iteration starts from 60, becasue the half window length is 60 data. Should we change the window size to 120, then we need to change the iteration start number into 120. Scoring criteria that I use in this case is Pearson’s Criterion, that the the best prediction will have score very close to 1.
4. Ranking
After iteration for each row in Hamilton 26 well, the score will be stored in ‘score’ variable. We can see that the maximum value is located in score index number 1021.
plt.figure(figsize=(10,3))
plt.plot(score_index, score)
plt.xlabel(‘Index Number’)
plt.xlabel(‘Corr Coef Value’)
in order to get the score index, and the depth of the best score we need to these following steps:
- Get the ‘best_score_index’ using ‘np.argmax(score)’. argmax can be used to find the index of maximum value in an array or list.
- Convert best score_index depth into ‘best_depth_index’ by using ‘score_index[dept]’
- Find the Depth of the best score in Terget Well by using ‘well2.loc[dept].DEPT’
best_score_index = np.argmax(score)
best_depth_index = score_index[best_score_index]
dept = well2.loc[best_depth_index].DEPTprint(‘Depth of Hutchinson Salt Member in Hudson 26:’, dept)
your corelation result will be stored in ‘dept’ variable. It said that the best score are in depth of 510m in Hudson 26 well.
plt.figure(figsize=(15,10))plt.subplot(141)
plt.title(‘Hamilton 24’)
plt.plot(‘GR_nor’, ‘DEPT’, data=well1)for i in marker.values:
plt.hlines(i[1], 0, 1,’red’,linestyles=’dashed’, alpha=0.5)
plt.text(0, i[1],i[0])
plt.ylim(0,800)
plt.gca().invert_yaxis()plt.subplot(142)
plt.title(‘Hamilton 26’)
plt.plot(‘GR_nor’, ‘DEPT’, data=well2)
plt.hlines(dept, 0, 1,’red’,linestyles=’dashed’, alpha=0.5)plt.text(0, dept,’Salt Member intepreted’)
plt.ylim(0,800)
plt.gca().invert_yaxis()plt.subplot(143)
plt.title(‘Corellation Coeff’)
x = score
y = well2.loc[score_index,’DEPT’]
plt.plot(x,y)
plt.ylim(0,800)
plt.gca().invert_yaxis()
Conclusion and Improvement
Actually there are many sophisticated corelation method out there, this is jut one of the basic automatic stratigraphic well corelation script using python that you can use in daily basis.
You might imporve this script in term of ease of use, performace, and accuracy by:
- Tuning the scoring parameter with RMSE, MAE, and many other metrices.
- Using machine learning for searching.
- Using advance indexing and labeling for quicken the process.
- Use paralel computing, or use C or Fortran backend with CuPy, Cyton, or Numba.
- Build functions that make you able to predict every single horizons.