\n",
14 | "\n",
15 | "## GeostatsPy Well-documented Demonstration Workflows \n",
16 | "\n",
17 | "### Bootstrap for Uncertainty Quantification\n",
18 | "\n",
19 | "#### Michael Pyrcz, Professor, The University of Texas at Austin \n",
20 | "\n",
21 | "##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "This is a tutorial for / demonstration of **Bootstrap**. \n",
29 | "\n",
30 | "**YouTube Lecture**: check out my lecture on [Bootstrap](https://youtu.be/wCgdoImlLY0?si=lpTWz2H7QTdxHBy9). For your convenience here's a summary of salient points.\n",
31 | "\n",
32 | "\n",
33 | "#### Bootstrap\n",
34 | "\n",
35 | "Uncertainty in the sample statistics\n",
36 | "* one source of uncertainty is the paucity of data.\n",
37 | "* do 200 or even less wells provide a precise (and accurate estimate) of the mean? standard deviation? skew? P13?\n",
38 | "\n",
39 | "Would it be useful to know the uncertainty in these statistics due to limited sampling?\n",
40 | "* what is the impact of uncertainty in the mean porosity e.g. 20%+/-2%?\n",
41 | "\n",
42 | "**Bootstrap** is a method to assess the uncertainty in a sample statistic by repeated random sampling with replacement.\n",
43 | "\n",
44 | "Assumptions\n",
45 | "* sufficient, representative sampling, identical, idependent samples\n",
46 | "\n",
47 | "Limitations\n",
48 | "1. assumes the samples are representative \n",
49 | "2. assumes stationarity\n",
50 | "3. only accounts for uncertainty due to too few samples, e.g. no uncertainty due to changes away from data\n",
51 | "4. does not account for boundary of area of interest \n",
52 | "5. assumes the samples are independent\n",
53 | "6. does not account for other local information sources\n",
54 | "\n",
55 | "The Bootstrap Approach (Efron, 1982)\n",
56 | "\n",
57 | "Statistical resampling procedure to calculate uncertainty in a calculated statistic from the data itself.\n",
58 | "* Does this work? Prove it to yourself, for uncertainty in the mean solution is standard error: \n",
59 | "\n",
60 | "\\begin{equation}\n",
61 | "\\sigma^2_\\overline{x} = \\frac{\\sigma^2_s}{n}\n",
62 | "\\end{equation}\n",
63 | "\n",
64 | "Extremely powerful - could calculate uncertainty in any statistic! e.g. P13, skew etc.\n",
65 | "* Would not be possible access general uncertainty in any statistic without bootstrap.\n",
66 | "* Advanced forms account for spatial information and sampling strategy (game theory and Journel’s spatial bootstrap (1993).\n",
67 | "\n",
68 | "Steps: \n",
69 | "\n",
70 | "1. assemble a sample set, must be representative, reasonable to assume independence between samples\n",
71 | "\n",
72 | "2. optional: build a cumulative distribution function (CDF)\n",
73 | " * may account for declustering weights, tail extrapolation\n",
74 | " * could use analogous data to support\n",
75 | "\n",
76 | "3. For $\\ell = 1, \\ldots, L$ realizations, do the following:\n",
77 | "\n",
78 | " * For $i = \\alpha, \\ldots, n$ data, do the following:\n",
79 | "\n",
80 | " * Draw a random sample with replacement from the sample set or Monte Carlo simulate from the CDF (if available). \n",
81 | "\n",
82 | "6. Calculate a realization of the sammary statistic of interest from the $n$ samples, e.g. $m^\\ell$, $\\sigma^2_{\\ell}$. Return to 3 for another realization.\n",
83 | "\n",
84 | "7. Compile and summarize the $L$ realizations of the statistic of interest.\n",
85 | "\n",
86 | "This is a very powerful method. Let's try it out.\n",
87 | "\n",
88 | "#### Load the required libraries\n",
89 | "\n",
90 | "The following code loads the required libraries. "
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": 1,
96 | "metadata": {},
97 | "outputs": [
98 | {
99 | "name": "stdout",
100 | "output_type": "stream",
101 | "text": [
102 | "GeostatsPy version: 0.0.57\n"
103 | ]
104 | }
105 | ],
106 | "source": [
107 | "import geostatspy.GSLIB as GSLIB # GSLIB utilies, visualization and wrapper\n",
108 | "import geostatspy.geostats as geostats # GSLIB methods convert to Python \n",
109 | "import geostatspy\n",
110 | "print('GeostatsPy version: ' + str(geostatspy.__version__))"
111 | ]
112 | },
113 | {
114 | "cell_type": "markdown",
115 | "metadata": {},
116 | "source": [
117 | "#### Check Another Package\n",
118 | "\n",
119 | "At some point you are going to run into an issue with package versions. This is how you can check packages."
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": 2,
125 | "metadata": {},
126 | "outputs": [
127 | {
128 | "data": {
129 | "text/plain": [
130 | "'1.26.4'"
131 | ]
132 | },
133 | "execution_count": 2,
134 | "metadata": {},
135 | "output_type": "execute_result"
136 | }
137 | ],
138 | "source": [
139 | "import numpy\n",
140 | "numpy.__version__"
141 | ]
142 | },
143 | {
144 | "cell_type": "markdown",
145 | "metadata": {},
146 | "source": [
147 | "We will also need some standard packages. These should have been installed with Anaconda 3."
148 | ]
149 | },
150 | {
151 | "cell_type": "code",
152 | "execution_count": 3,
153 | "metadata": {},
154 | "outputs": [],
155 | "source": [
156 | "ignore_warnings = True # ignore warnings?\n",
157 | "import numpy as np # ndarrys for gridded data\n",
158 | "import pandas as pd # DataFrames for tabular data\n",
159 | "import os # set working directory, run executables\n",
160 | "import matplotlib.pyplot as plt # for plotting\n",
161 | "from matplotlib.ticker import (MultipleLocator, AutoMinorLocator) # control of axes ticks\n",
162 | "from scipy import stats # summary statistics\n",
163 | "import math # trig etc.\n",
164 | "import scipy.signal as signal # kernel for moving window calculation\n",
165 | "import random\n",
166 | "plt.rc('axes', axisbelow=True) # plot all grids below the plot elements\n",
167 | "if ignore_warnings == True: \n",
168 | " import warnings\n",
169 | " warnings.filterwarnings('ignore')\n",
170 | "cmap = plt.cm.inferno # color map"
171 | ]
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "metadata": {},
176 | "source": [
177 | "If you get a package import error, you may have to first install some of these packages. This can usually be accomplished by opening up a command window on Windows and then typing 'python -m pip install [package-name]'. More assistance is available with the respective package docs. \n",
178 | "\n",
179 | "#### Declare functions\n",
180 | "\n",
181 | "These are some of the functions from GeostatsPy required by the new program. We will declare them here and then in the future integrate the new indicator kriging program into the package properly."
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": 4,
187 | "metadata": {},
188 | "outputs": [],
189 | "source": [
190 | "def add_grid():\n",
191 | " plt.gca().grid(True, which='major',linewidth = 1.0); plt.gca().grid(True, which='minor',linewidth = 0.2) # add y grids\n",
192 | " plt.gca().tick_params(which='major',length=7); plt.gca().tick_params(which='minor', length=4)\n",
193 | " plt.gca().xaxis.set_minor_locator(AutoMinorLocator()); plt.gca().yaxis.set_minor_locator(AutoMinorLocator()) # turn on minor ticks "
194 | ]
195 | },
196 | {
197 | "cell_type": "markdown",
198 | "metadata": {},
199 | "source": [
200 | "#### Set the working directory\n",
201 | "\n",
202 | "I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time). "
203 | ]
204 | },
205 | {
206 | "cell_type": "code",
207 | "execution_count": null,
208 | "metadata": {},
209 | "outputs": [],
210 | "source": [
211 | "#os.chdir(\"c:/PGE383\") # set the working directory"
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "metadata": {},
217 | "source": [
218 | "#### Loading Tabular Data\n",
219 | "\n",
220 | "Here's the command to load our comma delimited data file in to a Pandas' DataFrame object. "
221 | ]
222 | },
223 | {
224 | "cell_type": "code",
225 | "execution_count": null,
226 | "metadata": {},
227 | "outputs": [],
228 | "source": [
229 | "df = pd.read_csv(r'https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/sample_data_biased.csv') # load our data form my GitHub account"
230 | ]
231 | },
232 | {
233 | "cell_type": "markdown",
234 | "metadata": {},
235 | "source": [
236 | "Let's drop some samples so that we increase the variations in bootstrap samples for our demonstration below.\n",
237 | "\n",
238 | "* Warning, this is not a standard part of the bootstrap workflow, I'm doing this so my students can change the number of data and observe the impact on uncertainty."
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "metadata": {},
245 | "outputs": [],
246 | "source": [
247 | "df = df.sample(frac = 0.2) # extract 50 random samples to reduce the size of the dataset \n",
248 | "print('Using ' + str(len(df)) + ' number of samples')"
249 | ]
250 | },
251 | {
252 | "cell_type": "markdown",
253 | "metadata": {},
254 | "source": [
255 | "Visualizing the DataFrame would be useful and we already learned about these methods in this demo (https://git.io/fNgRW). \n",
256 | "\n",
257 | "We can preview the DataFrame by printing a slice or by utilizing the 'head' DataFrame member function (with a nice and clean format, see below). With the slice we could look at any subset of the data table and with the head command, add parameter 'n=13' to see the first 13 rows of the dataset. "
258 | ]
259 | },
260 | {
261 | "cell_type": "code",
262 | "execution_count": null,
263 | "metadata": {},
264 | "outputs": [],
265 | "source": [
266 | "df.head(n=3) # DataFrame preview to check"
267 | ]
268 | },
269 | {
270 | "cell_type": "markdown",
271 | "metadata": {},
272 | "source": [
273 | "#### Summary Statistics for Tabular Data\n",
274 | "\n",
275 | "The table includes X and Y coordinates (meters), Facies 1 and 0 (1 is sandstone and 0 interbedded sand and mudstone), Porosity (fraction), and permeability as Perm (mDarcy). \n",
276 | "\n",
277 | "There are a lot of efficient methods to calculate summary statistics from tabular data in DataFrames. The describe command provides count, mean, minimum, maximum, and quartiles all in a nice data table. We use transpose just to flip the table so that features are on the rows and the statistics are on the columns."
278 | ]
279 | },
280 | {
281 | "cell_type": "code",
282 | "execution_count": null,
283 | "metadata": {},
284 | "outputs": [],
285 | "source": [
286 | "df.describe().transpose() # summary statistics"
287 | ]
288 | },
289 | {
290 | "cell_type": "markdown",
291 | "metadata": {},
292 | "source": [
293 | "#### Visualizing Tabular Data with Location Maps \n",
294 | "\n",
295 | "It is natural to set the x and y coordinate and feature ranges manually. e.g. do you want your color bar to go from 0.05887 to 0.24230 exactly? Also, let's pick a color map for display. I heard that plasma is known to be friendly to the color blind as the color and intensity vary together (hope I got that right, it was an interesting Twitter conversation started by Matt Hall from Agile if I recall correctly). We will assume a study area of 0 to 1,000m in x and y and omit any data outside this area."
296 | ]
297 | },
298 | {
299 | "cell_type": "code",
300 | "execution_count": null,
301 | "metadata": {},
302 | "outputs": [],
303 | "source": [
304 | "xmin = 0.0; xmax = 1000.0 # range of x values\n",
305 | "ymin = 0.0; ymax = 1000.0 # range of y values\n",
306 | "pormin = 0.05; pormax = 0.25; # range of porosity values\n",
307 | "nx = 100; ny = 100; csize = 10.0 "
308 | ]
309 | },
310 | {
311 | "cell_type": "markdown",
312 | "metadata": {},
313 | "source": [
314 | "Let's try out locmap. This is a reimplementation of GSLIB's locmap program that uses matplotlib. I hope you find it simpler than matplotlib, if you want to get more advanced and build custom plots lock at the source. If you improve it, send me the new code. Any help is appreciated. To see the parameters, just type the command name:"
315 | ]
316 | },
317 | {
318 | "cell_type": "code",
319 | "execution_count": null,
320 | "metadata": {},
321 | "outputs": [],
322 | "source": [
323 | "GSLIB.locmap # GeostatsPy's 2D point plot function"
324 | ]
325 | },
326 | {
327 | "cell_type": "markdown",
328 | "metadata": {},
329 | "source": [
330 | "Now we can populate the plotting parameters and visualize the porosity data."
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": null,
336 | "metadata": {},
337 | "outputs": [],
338 | "source": [
339 | "plt.subplot(111)\n",
340 | "GSLIB.locmap_st(df,'X','Y','Porosity',xmin,xmax,ymin,ymax,pormin,pormax,'Well Data - Porosity','X(m)','Y(m)','Porosity (fraction)',cmap)\n",
341 | "plt.subplots_adjust(left=0.0, bottom=0.0, right=1.0, top=1.1, wspace=0.2, hspace=0.2); plt.show()"
342 | ]
343 | },
344 | {
345 | "cell_type": "markdown",
346 | "metadata": {},
347 | "source": [
348 | "#### A Very Simple Bootstrap Method in Python \n",
349 | "\n",
350 | "If you are new to bootstrap and Python, here's the most simple code possible for bootstrap.\n",
351 | "\n",
352 | "* specify the number of bootstrap realizations, $L$\n",
353 | "* declare a list to store the bootstrap realizations of the statistic of interest\n",
354 | "* loop over L bootstrap realizations\n",
355 | " * n MCS, random samples with replacement for a new realization of the data\n",
356 | " * calculate the realization of the statistic from the realization of the data\n",
357 | "* summarize the resulting uncertainty model, histogram, summary statistics etc."
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": null,
363 | "metadata": {},
364 | "outputs": [],
365 | "source": [
366 | "import random # import random package\n",
367 | "L = 1000 # set the number of bootstrap realizations \n",
368 | "por_avg_real = [] # declare an empty list to store the bootstrap realizations of the statistic \n",
369 | "for k in range(0,L): # loop over the L bootstrap realizations\n",
370 | " samples = random.choices(df['Porosity'].values, k=len(df)) # n Monte Carlo simulations\n",
371 | " por_avg_real.append(np.average(samples)) # calculate the statistic of interest from the new bootstrap dataset\n",
372 | "plt.hist(por_avg_real,color = 'darkorange',alpha = 0.8,edgecolor = 'black') # plot the distribution, could also calculate any summary statistics\n",
373 | "plt.xlabel('Boostrap Realizations of Average Porosity'); plt.ylabel('Frequency'); plt.title('Uncertainty Distribution for Average Porosity')\n",
374 | "plt.subplots_adjust(left=0.0, bottom=0.0, right=1.0, top=1.1, wspace=0.2, hspace=0.2); plt.show()"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "metadata": {},
380 | "source": [
381 | "Now we proceed with a more complicated, robust work by first quantifying the spatial bias in the samples and assigned data weights to mitigate this bias.\n",
382 | "\n",
383 | "#### Declustering\n",
384 | "\n",
385 | "Let's calculate some declustering weights. There is a demonstration on declustering here https://git.io/fhgJl if you need more information. "
386 | ]
387 | },
388 | {
389 | "cell_type": "code",
390 | "execution_count": null,
391 | "metadata": {},
392 | "outputs": [],
393 | "source": [
394 | "wts, cell_sizes, dmeans = geostats.declus(df,'X','Y','Porosity',iminmax = 1, noff= 10, ncell=100,cmin=10,cmax=2000)\n",
395 | "df['Wts'] = wts # add weights to the sample data DataFrame\n",
396 | "df.head() # preview to check the sample data DataFrame\n",
397 | "\n",
398 | "def weighted_avg_and_std(values, weights): # function to calculate weighted mean and st. dev., from Eric O Lebigot, stack overflow,\n",
399 | " average = np.average(values, weights=weights)\n",
400 | " variance = np.average((values-average)**2, weights=weights)\n",
401 | " return (average, math.sqrt(variance))\n",
402 | "\n",
403 | "sample_avg, sample_stdev = weighted_avg_and_std(df['Porosity'],df['Wts'])\n",
404 | "print('Declustered mean = ' + str(round(sample_avg,3)) + ' and declustered standard deviation = ' + str(round(sample_stdev,3)))"
405 | ]
406 | },
407 | {
408 | "cell_type": "markdown",
409 | "metadata": {},
410 | "source": [
411 | "##### A Couple of Bootstrap Realizations\n",
412 | "\n",
413 | "We will attempt boostrap by-hand and manually loop over $L$ realizations and draw $n$ samples to calculate the summary statistics of interest, mean and variance. The choice function from the random package simplifies sampling with replacement from a set of samples with weights.\n",
414 | "\n",
415 | "This command returns a ndarray with k samples with replacment from the 'Porosity' column of our DataFrame (df) accounting for the data weights in column 'Wts'.\n",
416 | "```p\n",
417 | "samples1 = random.choices(df['Porosity'].values, weights=df['Wts'].values, cum_weights=None, k=len(df))\n",
418 | "```\n",
419 | "\n",
420 | "It is instructive to look at a couple of these realizations from the original declustered data set."
421 | ]
422 | },
423 | {
424 | "cell_type": "code",
425 | "execution_count": null,
426 | "metadata": {},
427 | "outputs": [],
428 | "source": [
429 | "samples1 = random.choices(df['Porosity'].values, weights=df['Wts'].values, cum_weights=None, k=len(df))\n",
430 | "samples2 = random.choices(df['Porosity'].values, weights=df['Wts'].values, cum_weights=None, k=len(df))\n",
431 | "\n",
432 | "print('Bootstrap means, realization 1 = ' + str(np.average(samples1)) + ' and realization 2 = ' + str(np.average(samples2)))\n",
433 | "\n",
434 | "plt.subplot(131)\n",
435 | "GSLIB.hist_st(df['Porosity'],pormin,pormax,False,False,20,df['Wts'],'Porosity (fraction)','Histogram Declustered Porosity')\n",
436 | "\n",
437 | "plt.subplot(132)\n",
438 | "GSLIB.hist_st(samples1,pormin,pormax,False,False,20,None,'Bootstrap Sample - Realizaton 1','Histogram Bootstrap Porosity 1')\n",
439 | "\n",
440 | "plt.subplot(133)\n",
441 | "GSLIB.hist_st(samples2,pormin,pormax,False,False,20,None,'Bootstrap Sample - Realizaton 2','Histogram Bootstrap Porosity 2')\n",
442 | "\n",
443 | "plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.1, wspace=0.2, hspace=0.2)\n",
444 | "plt.show()"
445 | ]
446 | },
447 | {
448 | "cell_type": "markdown",
449 | "metadata": {},
450 | "source": [
451 | "Note that the bootstrap distributions vary quite a bit from the original.\n",
452 | "\n",
453 | "#### Summarizations Over Bootstrap Realizations\n",
454 | "\n",
455 | "Let's make a loop to conduct $L$ resamples and calculate the average and standard deviation for each ($m^\\ell$, $\\sigma^2_{\\ell}$, for $\\ell = 0,\\dots,L-1$). We then summarization over these $L$ realizations. \n",
456 | "\n",
457 | "I did not find any built-in, concise functions to accomplish this, i.e. with a single line of code, so we are going to do it by hand. \n",
458 | "\n",
459 | "To understand this code there are just a couple of Python concepts that you need to add to your Python arsenal.\n",
460 | "\n",
461 | "1. declaring arrays - NumPy has a lot of great array (ndarray) functionality. There are build in functions to make a ndarray of any length (and dimension). This includes 'zeros', 'ones' and 'rand', so when we use this code:\n",
462 | "\n",
463 | "```p\n",
464 | "mean = np.zeros(L); stdev = np.zeros(L)\n",
465 | "```\n",
466 | "\n",
467 | " we're making arrays of length $L$ pre-populated with zeros.\n",
468 | "\n",
469 | "2. For Loops - when we are using the command below, we are instructing the computer to loop over all the indented code below the command for $l = 0,1,2,\\ldots,L-1$ times. For each loop the $l$ variable increments, so we can use this to save each result to a different index in the arrays mean and stdev. Note, Python arrays index starting at 0 and stop at the length - 1.\n",
470 | "\n",
471 | "```p\n",
472 | "for l in range(0, L): \n",
473 | "```\n",
474 | "\n",
475 | " we are running each bootstrap resampled realization, calculating the average and standard deviation and storing them in the arrays that we already declared."
476 | ]
477 | },
478 | {
479 | "cell_type": "code",
480 | "execution_count": null,
481 | "metadata": {},
482 | "outputs": [],
483 | "source": [
484 | "L = 1000 # set the number of realizations\n",
485 | "mean = np.zeros(L); stdev = np.zeros(L) # declare arrays to hold the realizations of the statistics\n",
486 | "for l in range(0, L): # loop over realizations\n",
487 | " samples = random.choices(df['Porosity'].values, weights=df['Wts'].values, cum_weights=None, k=len(df))\n",
488 | " mean[l] = np.average(samples)\n",
489 | " stdev[l] = np.std(samples)\n",
490 | " \n",
491 | "plt.subplot(121)\n",
492 | "GSLIB.hist_st(mean,0.11,0.15,False,False,50,None,'Average Porosity (fraction)','Bootstrap Uncertainty in Porosity Average')\n",
493 | "\n",
494 | "plt.subplot(122)\n",
495 | "GSLIB.hist_st(stdev,0.015,0.045,False,False,50,None,'Standard Deviation Porosity (fraction)','Bootstrap Uncertainty in Porosity Standard Deviation')\n",
496 | "\n",
497 | "plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.2, wspace=0.2, hspace=0.2)\n",
498 | "plt.show() \n",
499 | " \n",
500 | "print('Summary Statistics for Bootstrap Porosity Mean Realizations:')\n",
501 | "print(stats.describe(mean))\n",
502 | "print('P10: ' + str(round(np.percentile(mean,10),3)) + ', P50: ' + str(round(np.percentile(mean,50),3)) + ', P90: ' + str(round(np.percentile(mean,90),3))) \n",
503 | "\n",
504 | "print('\\nSummary Statistics for Bootstrap Porosity Standard Deviation Realizations:')\n",
505 | "print(stats.describe(stdev))\n",
506 | "print('P10: ' + str(round(np.percentile(stdev,10),3)) + ', P50: ' + str(round(np.percentile(stdev,50),3)) + ', P90: ' + str(round(np.percentile(stdev,90),3))) "
507 | ]
508 | },
509 | {
510 | "cell_type": "markdown",
511 | "metadata": {},
512 | "source": [
513 | "#### Bootstrap in GeostatsPy\n",
514 | "\n",
515 | "We have a simple bootstrap function in GeostatsPy\n",
516 | "\n",
517 | "```python\n",
518 | "realizations_array = geostats.bootstrap(array,stat,weights=None,nreal=100)\n",
519 | "```\n",
520 | "\n",
521 | "where:\n",
522 | " \n",
523 | "* array - an numpy ndarray of samples\n",
524 | "* stat - statistic function that can be applied to an ndarray, e.g., numpy.average\n",
525 | "* weights - an array of weights, if omitted or set to None, then equal weighting is applied\n",
526 | "* nreal - the number of realizations"
527 | ]
528 | },
529 | {
530 | "cell_type": "code",
531 | "execution_count": null,
532 | "metadata": {},
533 | "outputs": [],
534 | "source": [
535 | "L = 10000\n",
536 | "mean = geostats.bootstrap(df['Porosity'].values,stat = np.average,weights = df['Wts'].values,nreal = L)\n",
537 | "stdev = geostats.bootstrap(df['Porosity'].values,stat = np.std,weights = df['Wts'].values,nreal = L)\n",
538 | "\n",
539 | "plt.subplot(121)\n",
540 | "GSLIB.hist_st(mean,0.11,0.15,False,False,50,None,'Average Porosity (fraction)','Bootstrap Uncertainty in Porosity Average')\n",
541 | "\n",
542 | "plt.subplot(122)\n",
543 | "GSLIB.hist_st(stdev,0.015,0.045,False,False,50,None,'Standard Deviation Porosity (fraction)','Bootstrap Uncertainty in Porosity Standard Deviation')\n",
544 | "\n",
545 | "plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=1.2, wspace=0.2, hspace=0.2)\n",
546 | "plt.show() \n",
547 | " \n",
548 | "print('Summary Statistics for Bootstrap Porosity Mean Realizations:')\n",
549 | "print(stats.describe(mean))\n",
550 | "print('P10: ' + str(round(np.percentile(mean,10),3)) + ', P50: ' + str(round(np.percentile(mean,50),3)) + ', P90: ' + str(round(np.percentile(mean,90),3))) \n",
551 | "\n",
552 | "print('\\nSummary Statistics for Bootstrap Porosity Standard Deviation Realizations:')\n",
553 | "print(stats.describe(stdev))\n",
554 | "print('P10: ' + str(round(np.percentile(stdev,10),3)) + ', P50: ' + str(round(np.percentile(stdev,50),3)) + ', P90: ' + str(round(np.percentile(stdev,90),3))) "
555 | ]
556 | },
557 | {
558 | "cell_type": "markdown",
559 | "metadata": {},
560 | "source": [
561 | "#### Comments\n",
562 | "\n",
563 | "This was a basic demonstration of trend modeling to support 3D model construction. Much more can be done, I have other demonstrations for modeling workflows with GeostatsPy in the GitHub repository [GeostatsPy_Demos](https://github.com/GeostatsGuy/GeostatsPy_Demos/tree/main).\n",
564 | "\n",
565 | "I hope this is helpful,\n",
566 | "\n",
567 | "*Michael*\n",
568 | "\n",
569 | "#### The Author:\n",
570 | "\n",
571 | "### Michael Pyrcz, Professor, The University of Texas at Austin \n",
572 | "*Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*\n",
573 | "\n",
574 | "With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development. \n",
575 | "\n",
576 | "For more about Michael check out these links:\n",
577 | "\n",
578 | "#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
579 | "\n",
580 | "#### Want to Work Together?\n",
581 | "\n",
582 | "I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.\n",
583 | "\n",
584 | "* Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you! \n",
585 | "\n",
586 | "* Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!\n",
587 | "\n",
588 | "* I can be reached at mpyrcz@austin.utexas.edu.\n",
589 | "\n",
590 | "I'm always happy to discuss,\n",
591 | "\n",
592 | "*Michael*\n",
593 | "\n",
594 | "Michael Pyrcz, Ph.D., P.Eng. Professor, Cockrell School of Engineering and The Jackson School of Geosciences, The University of Texas at Austin\n",
595 | "\n",
596 | "#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1) \n",
597 | " "
598 | ]
599 | },
600 | {
601 | "cell_type": "code",
602 | "execution_count": null,
603 | "metadata": {},
604 | "outputs": [],
605 | "source": []
606 | }
607 | ],
608 | "metadata": {
609 | "kernelspec": {
610 | "display_name": "Python 3 (ipykernel)",
611 | "language": "python",
612 | "name": "python3"
613 | },
614 | "language_info": {
615 | "codemirror_mode": {
616 | "name": "ipython",
617 | "version": 3
618 | },
619 | "file_extension": ".py",
620 | "mimetype": "text/x-python",
621 | "name": "python",
622 | "nbconvert_exporter": "python",
623 | "pygments_lexer": "ipython3",
624 | "version": "3.11.4"
625 | }
626 | },
627 | "nbformat": 4,
628 | "nbformat_minor": 2
629 | }
630 |
--------------------------------------------------------------------------------
/GeostatsPy_fence_diagram.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "Install packages you need for the plots"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {
14 | "ExecuteTime": {
15 | "end_time": "2023-03-30T18:03:40.643063Z",
16 | "start_time": "2023-03-30T18:03:40.628103Z"
17 | }
18 | },
19 | "outputs": [],
20 | "source": [
21 | "# pip install pyvista # the plotting package"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "metadata": {
28 | "ExecuteTime": {
29 | "end_time": "2023-03-30T17:56:30.732702Z",
30 | "start_time": "2023-03-30T17:56:30.711759Z"
31 | }
32 | },
33 | "outputs": [],
34 | "source": [
35 | "# pip install trame"
36 | ]
37 | },
38 | {
39 | "cell_type": "code",
40 | "execution_count": null,
41 | "metadata": {
42 | "ExecuteTime": {
43 | "end_time": "2023-03-30T17:56:38.552053Z",
44 | "start_time": "2023-03-30T17:56:36.306992Z"
45 | }
46 | },
47 | "outputs": [],
48 | "source": [
49 | "# pip install ipywidgets # support package"
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "metadata": {},
55 | "source": [
56 | "Import the packages."
57 | ]
58 | },
59 | {
60 | "cell_type": "code",
61 | "execution_count": 1,
62 | "metadata": {
63 | "ExecuteTime": {
64 | "end_time": "2023-03-30T18:04:37.325807Z",
65 | "start_time": "2023-03-30T18:04:36.183792Z"
66 | }
67 | },
68 | "outputs": [
69 | {
70 | "name": "stderr",
71 | "output_type": "stream",
72 | "text": [
73 | "C:\\Users\\pm27995\\Anaconda3\\lib\\site-packages\\numpy\\_distributor_init.py:30: UserWarning: loaded more than 1 DLL from .libs:\n",
74 | "C:\\Users\\pm27995\\Anaconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.GK7GX5KEQ4F6UYO3P26ULGBQYHGQO7J4.gfortran-win_amd64.dll\n",
75 | "C:\\Users\\pm27995\\Anaconda3\\lib\\site-packages\\numpy\\.libs\\libopenblas.XWYDX2IKJW2NMTWSFYNGFUWKQU3LYTCZ.gfortran-win_amd64.dll\n",
76 | " warnings.warn(\"loaded more than 1 DLL from .libs:\"\n"
77 | ]
78 | }
79 | ],
80 | "source": [
81 | "import numpy as np\n",
82 | "import pyvista as pv\n",
83 | "import matplotlib.pyplot as plt"
84 | ]
85 | },
86 | {
87 | "cell_type": "markdown",
88 | "metadata": {},
89 | "source": [
90 | "Create a random cube."
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": 2,
96 | "metadata": {
97 | "ExecuteTime": {
98 | "end_time": "2023-03-30T18:04:41.706826Z",
99 | "start_time": "2023-03-30T18:04:41.589205Z"
100 | }
101 | },
102 | "outputs": [],
103 | "source": [
104 | "cube = np.random.randint(low=0, high=20, size=(200, 200, 200))"
105 | ]
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "metadata": {},
110 | "source": [
111 | "Start plotting!"
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "execution_count": 12,
117 | "metadata": {
118 | "ExecuteTime": {
119 | "end_time": "2023-03-30T18:06:17.060107Z",
120 | "start_time": "2023-03-30T18:06:15.835108Z"
121 | },
122 | "scrolled": false
123 | },
124 | "outputs": [
125 | {
126 | "data": {
127 | "image/png": "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\n",
128 | "text/plain": [
129 | "
\n",
11 | "\n",
12 | "## GeostatsPy Well-documented Demonstration Workflows \n",
13 | "\n",
14 | "### Sequential Gaussian Simulation (SGSIM) Maps\n",
15 | "\n",
16 | "#### Michael Pyrcz, Professor, The University of Texas at Austin \n",
17 | "\n",
18 | "##### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "metadata": {},
24 | "source": [
25 | "This is a tutorial for / demonstration of **Spatial Simulation with Sequential Gaussian Simulation (SGSIM)** with a 2D map example.\n",
26 | "\n",
27 | "* sequential Gaussian simulation is a common geostatistical method for calculating spatial models with an appropriate level of heterogeneity\n",
28 | "\n",
29 | "\n",
30 | "**YouTube Lecture**: check out my lecture on [Stochastic Simulation](https://youtu.be/3cLqK3lR56Y?si=3WhHX5QOZrrtlgAI). For your convenience here's a summary of salient points.\n",
31 | "\n",
32 | "#### Estimation vs. Simulation\n",
33 | "\n",
34 | "Let's start by comparing spatial estimation and simulation.\n",
35 | "\n",
36 | "Estimation: \n",
37 | "\n",
38 | "* honors local data \n",
39 | "* locally accurate, primary goal of estimation is 1 estimate! \n",
40 | "* too smooth, appropriate for visualizing trends \n",
41 | "* too smooth, inappropriate for flow simulation \n",
42 | "* one model, no assessment of global uncertainty \n",
43 | "\n",
44 | "Simulation: \n",
45 | "\n",
46 | "* honors local data \n",
47 | "* sacrifices local accuracy, reproduces histogram \n",
48 | "* honors spatial variability, appropriate for flow simulation \n",
49 | "* alternative realizations, change random number seed \n",
50 | "* many models (realizations), assessment of global uncertainty\n",
51 | "\n",
52 | "No let's explain the concept of spatial simulation.\n",
53 | "\n",
54 | "#### Spatial Simulation\n",
55 | "\n",
56 | "This method is critical for:\n",
57 | "\n",
58 | "1. Prediction away from wells, e.g. pre-drill assessments, with uncertainty \n",
59 | "2. Spatial uncertainty modeling.\n",
60 | "3. Heterogeneity realizations ready for application to the transfer function.\n",
61 | "\n",
62 | "#### Sequential Gaussian Simulation\n",
63 | "\n",
64 | "With sequential Gaussian simulation we build on kriging by:\n",
65 | "\n",
66 | "* adding a random residual with the missing variance\n",
67 | "\n",
68 | "* sequentially adding the simulated values as data to correct the covariance between the simulated values\n",
69 | "\n",
70 | "The resulting model corrects the issues of kriging, as we now:\n",
71 | "\n",
72 | "* reproduce the global feature PDF / CDF\n",
73 | "\n",
74 | "* reproduce the global variogram\n",
75 | "\n",
76 | "* while providing a model of uncertainty through multiple realizations\n",
77 | "\n",
78 | "In this workflow we run multiple simulation realizations, and check the statistics.\n",
79 | "\n",
80 | "#### Load the required libraries\n",
81 | "\n",
82 | "The following code loads the required libraries. "
83 | ]
84 | },
85 | {
86 | "cell_type": "code",
87 | "execution_count": 1,
88 | "metadata": {},
89 | "outputs": [
90 | {
91 | "name": "stdout",
92 | "output_type": "stream",
93 | "text": [
94 | "GeostatsPy version: 0.0.66\n"
95 | ]
96 | }
97 | ],
98 | "source": [
99 | "import geostatspy.GSLIB as GSLIB # GSLIB utilies, visualization and wrapper\n",
100 | "import geostatspy.geostats as geostats # GSLIB methods convert to Python \n",
101 | "import geostatspy\n",
102 | "print('GeostatsPy version: ' + str(geostatspy.__version__))"
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "metadata": {},
108 | "source": [
109 | "We will also need some standard packages. These should have been installed with Anaconda 3."
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": 2,
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "ignore_warnings = True # ignore warnings?\n",
119 | "import numpy as np # ndarrys for gridded data\n",
120 | "import pandas as pd # DataFrames for tabular data\n",
121 | "import os # set working directory, run executables\n",
122 | "import matplotlib.pyplot as plt # for plotting\n",
123 | "from matplotlib.ticker import (MultipleLocator, AutoMinorLocator) # control of axes ticks\n",
124 | "from matplotlib import gridspec # custom subplots\n",
125 | "plt.rc('axes', axisbelow=True) # plot all grids below the plot elements\n",
126 | "if ignore_warnings == True: \n",
127 | " import warnings\n",
128 | " warnings.filterwarnings('ignore')\n",
129 | "from IPython.utils import io # mute output from simulation\n",
130 | "cmap = plt.cm.inferno # color map"
131 | ]
132 | },
133 | {
134 | "cell_type": "markdown",
135 | "metadata": {},
136 | "source": [
137 | "If you get a package import error, you may have to first install some of these packages. This can usually be accomplished by opening up a command window on Windows and then typing 'python -m pip install [package-name]'. More assistance is available with the respective package docs. \n",
138 | "\n",
139 | "#### Declare Functions\n",
140 | "\n",
141 | "Here's a convenience function for plotting variograms."
142 | ]
143 | },
144 | {
145 | "cell_type": "code",
146 | "execution_count": 3,
147 | "metadata": {},
148 | "outputs": [],
149 | "source": [
150 | "def add_grid():\n",
151 | " plt.gca().grid(True, which='major',linewidth = 1.0); plt.gca().grid(True, which='minor',linewidth = 0.2) # add y grids\n",
152 | " plt.gca().tick_params(which='major',length=7); plt.gca().tick_params(which='minor', length=4)\n",
153 | " plt.gca().xaxis.set_minor_locator(AutoMinorLocator()); plt.gca().yaxis.set_minor_locator(AutoMinorLocator()) # turn on minor ticks \n",
154 | "\n",
155 | "def vargplot(feature,lags,gamma_maj,gamma_min,npps_maj,npps_min,vmodel,azi,atol,sill): # plot the variogram\n",
156 | " index_maj,lags_maj,gmod_maj,cov_maj,ro_maj = geostats.vmodel(nlag=100,xlag=10,azm=azi,vario=vmodel);\n",
157 | " index_min,lags_min,gmod_min,cov_min,ro_min = geostats.vmodel(nlag=100,xlag=10,azm=azi+90.0,vario=vmodel);\n",
158 | " \n",
159 | " plt.scatter(lags,gamma_maj,color = 'black',s = npps_maj*0.01,label = 'Major Azimuth ' +str(azi), alpha = 0.8)\n",
160 | " plt.plot(lags_maj,gmod_maj,color = 'black')\n",
161 | " plt.scatter(lags,gamma_min,color = 'red',s = npps_min*0.01,label = 'Minor Azimuth ' +str(azi+90.0), alpha = 0.8)\n",
162 | " plt.plot(lags_min,gmod_min,color = 'red',label = 'Input Major Model')\n",
163 | " plt.plot([0,2000],[sill,sill],color = 'black',label = 'Input Minor Model')\n",
164 | " plt.xlabel(r'Lag Distance $\\bf(h)$, (m)')\n",
165 | " plt.ylabel(r'$\\gamma \\bf(h)$')\n",
166 | " if atol < 90.0:\n",
167 | " plt.title('Check Directional ' + feature + ' Variogram')\n",
168 | " else: \n",
169 | " plt.title('Check Omnidirectional NSCORE ' + feature + ' Variogram')\n",
170 | " plt.xlim([0,1000]); #plt.ylim([0,1.8])\n",
171 | " plt.legend(loc=\"lower right\")\n",
172 | " add_grid()"
173 | ]
174 | },
175 | {
176 | "cell_type": "markdown",
177 | "metadata": {},
178 | "source": [
179 | "#### Set the working directory\n",
180 | "\n",
181 | "I always like to do this so I don't lose files and to simplify subsequent read and writes (avoid including the full address each time). "
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": 4,
187 | "metadata": {},
188 | "outputs": [],
189 | "source": [
190 | "#os.chdir(\"c:/PGE383\") # set the working directory"
191 | ]
192 | },
193 | {
194 | "cell_type": "markdown",
195 | "metadata": {},
196 | "source": [
197 | "#### Loading Tabular Data\n",
198 | "\n",
199 | "Here's the command to load our comma delimited data file in to a Pandas' DataFrame object. \n",
200 | "\n",
201 | "* We will also extract a limited sample to reduce data density. This way we can observe more of the heterogeneity from the simulation with the spatial continuity model, rather than mostly data driven heterogeneity.\n",
202 | "\n",
203 | "* By setting unconditional to True the data are shifted well outside the area of interest and are only used for the target reference distribution"
204 | ]
205 | },
206 | {
207 | "cell_type": "code",
208 | "execution_count": 5,
209 | "metadata": {
210 | "scrolled": true
211 | },
212 | "outputs": [
213 | {
214 | "data": {
215 | "text/html": [
216 | "
\n",
217 | "\n",
230 | "
\n",
231 | " \n",
232 | "
\n",
233 | "
\n",
234 | "
X
\n",
235 | "
Y
\n",
236 | "
Facies
\n",
237 | "
Porosity
\n",
238 | "
Perm
\n",
239 | "
logPerm
\n",
240 | "
\n",
241 | " \n",
242 | " \n",
243 | "
\n",
244 | "
0
\n",
245 | "
270.0
\n",
246 | "
939.0
\n",
247 | "
1.0
\n",
248 | "
0.120364
\n",
249 | "
21.420613
\n",
250 | "
3.064354
\n",
251 | "
\n",
252 | "
\n",
253 | "
1
\n",
254 | "
410.0
\n",
255 | "
789.0
\n",
256 | "
1.0
\n",
257 | "
0.124565
\n",
258 | "
36.894248
\n",
259 | "
3.608056
\n",
260 | "
\n",
261 | "
\n",
262 | "
2
\n",
263 | "
820.0
\n",
264 | "
389.0
\n",
265 | "
0.0
\n",
266 | "
0.093900
\n",
267 | "
2.427176
\n",
268 | "
0.886729
\n",
269 | "
\n",
270 | "
\n",
271 | "
3
\n",
272 | "
800.0
\n",
273 | "
900.0
\n",
274 | "
1.0
\n",
275 | "
0.127497
\n",
276 | "
7.608928
\n",
277 | "
2.029322
\n",
278 | "
\n",
279 | "
\n",
280 | "
4
\n",
281 | "
870.0
\n",
282 | "
819.0
\n",
283 | "
0.0
\n",
284 | "
0.099368
\n",
285 | "
0.400079
\n",
286 | "
-0.916093
\n",
287 | "
\n",
288 | " \n",
289 | "
\n",
290 | "
"
291 | ],
292 | "text/plain": [
293 | " X Y Facies Porosity Perm logPerm\n",
294 | "0 270.0 939.0 1.0 0.120364 21.420613 3.064354\n",
295 | "1 410.0 789.0 1.0 0.124565 36.894248 3.608056\n",
296 | "2 820.0 389.0 0.0 0.093900 2.427176 0.886729\n",
297 | "3 800.0 900.0 1.0 0.127497 7.608928 2.029322\n",
298 | "4 870.0 819.0 0.0 0.099368 0.400079 -0.916093"
299 | ]
300 | },
301 | "execution_count": 5,
302 | "metadata": {},
303 | "output_type": "execute_result"
304 | }
305 | ],
306 | "source": [
307 | "unconditional = False\n",
308 | "df = pd.read_csv(r\"https://raw.githubusercontent.com/GeostatsGuy/GeoDataSets/master/sample_data_MV_biased.csv\") # from Dr. Pyrcz's GitHub repo\n",
309 | "df = df.sample(50) # extract 50 samples\n",
310 | "df = df.reset_index() # reset the record index \n",
311 | "df = df.drop(['index','Unnamed: 0','AI'],axis=1) # remove extra columns in DataFrame\n",
312 | "df['logPerm'] = np.log(df['Perm'].values) # calculate the log of permeability\n",
313 | "if unconditional == True:\n",
314 | " df['X'] = df['X'] + 9999999.9 # move the data outside the model, just use the distributions as a reference to constrain the simulated distributions\n",
315 | "df.head() # DataFrame summary"
316 | ]
317 | },
318 | {
319 | "cell_type": "markdown",
320 | "metadata": {},
321 | "source": [
322 | "#### Sequential Gaussian Simulation\n",
323 | "\n",
324 | "Let's jump right to building a variety of models with simulation and visualizing the results. We will start with a test of 3 realizations.\n",
325 | "\n",
326 | "* look at the realizations and check the histogram and variograms"
327 | ]
328 | },
329 | {
330 | "cell_type": "code",
331 | "execution_count": null,
332 | "metadata": {
333 | "scrolled": false
334 | },
335 | "outputs": [],
336 | "source": [
337 | "zmin = 0.00; zmax = 0.20 # feature min and max values \n",
338 | "nx = 80; ny = 80; xsiz = 10.0; ysiz = 10.0; xmn = 5.0; ymn = 5.0; nxdis = 1; nydis = 1 # grid specification\n",
339 | "nreal = 3 # number of realizations\n",
340 | "ndmin = 0; ndmax = 20 # number of data for each kriging system\n",
341 | "vario = GSLIB.make_variogram(nug=0.0,nst=1,it1=1,cc1=1.0,azi1=0.0,hmaj1=200,hmin1=50)\n",
342 | "tmin = -999; tmax = 999\n",
343 | "sill = np.std(df['Porosity'].values)*np.std(df['Porosity'].values)\n",
344 | "\n",
345 | "sim_sk = geostats.sgsim(df,'X','Y','Porosity',wcol=-1,scol=-1,tmin=tmin,tmax=tmax,itrans=1,ismooth=0,dftrans=0,tcol=0,\n",
346 | " twtcol=0,zmin=zmin,zmax=zmax,ltail=1,ltpar=0.0,utail=1,utpar=0.3,nsim=nreal,\n",
347 | " nx=nx,xmn=xmn,xsiz=xsiz,ny=ny,ymn=ymn,ysiz=ysiz,seed=73073,\n",
348 | " ndmin=ndmin,ndmax=ndmax,nodmax=20,mults=1,nmult=3,noct=-1,ktype=0,colocorr=0.0,sec_map=0,vario=vario)\n",
349 | "\n",
350 | "xmin = xmn-xsiz/2; xmax = nx*xsiz + xmin; ymin = ymn-ysiz/2; ymax = ny*ysiz + ymin; cmap = plt.cm.inferno # plotting parameters\n",
351 | "\n",
352 | "for isim in range(0,nreal):\n",
353 | " plt.subplot(nreal,3,1 + isim*3) # plot the results\n",
354 | " GSLIB.locpix_st(sim_sk[isim],xmin,xmax,ymin,ymax,xsiz,zmin,zmax,df,'X','Y','Porosity','Sequential Gaussian Simulation w. Simple Kriging #' + str(isim+1),'X(m)','Y(m)','Porosity',cmap)\n",
355 | " \n",
356 | " lags, gamma_x, npps_x = geostats.gam(sim_sk[isim],-9999.9,9999.9,xsiz,ysiz,ixd=1,iyd=0,nlag=100,isill=0)\n",
357 | " lags, gamma_y, npps_y = geostats.gam(sim_sk[isim],-9999.9,9999.9,xsiz,ysiz,ixd=0,iyd=1,nlag=100,isill=0)\n",
358 | "\n",
359 | " plt.subplot(nreal,3,2 + isim*3)\n",
360 | " plt.hist(sim_sk[isim].flatten(),bins=np.linspace(zmin,zmax,30),density=True,color='darkorange',alpha=0.6,edgecolor='black',zorder=10,label='Realization')\n",
361 | " plt.hist(df['Porosity'].values,bins=np.linspace(zmin,zmax,30),density=True,color='grey',alpha=0.5,edgecolor='black',zorder=1,label='Input')\n",
362 | " plt.legend(loc='upper left'); add_grid()\n",
363 | " plt.xlim([zmin,zmax]); plt.ylim([0,30]); plt.title('Check Histogram'); add_grid()\n",
364 | " \n",
365 | " plt.subplot(nreal,3,3 + isim*3)\n",
366 | " vario_plot = vario\n",
367 | " vario_plot.update({\"cc1\":sill})\n",
368 | " vargplot('Porosity',lags,gamma_x,gamma_y,npps_x,npps_y,vario_plot,azi=90.0,atol=22.5,sill=sill) # plot the variogram\n",
369 | " plt.xlim([0,1000]); plt.ylim([0.0,sill*2.0])\n",
370 | "\n",
371 | "plt.subplots_adjust(left=0.0, bottom=0.0, right=3.0, top=3.1, wspace=0.2, hspace=0.2); plt.show()"
372 | ]
373 | },
374 | {
375 | "cell_type": "markdown",
376 | "metadata": {},
377 | "source": [
378 | "#### Comments\n",
379 | "\n",
380 | "This was a basic demonstration of sequential Gaussian simulation to calculate spatial heterogeneity realizations that honor the data, histogram and variogram. Much more can be done, I have other demonstrations for modeling workflows with GeostatsPy in the GitHub repository [GeostatsPy_Demos](https://github.com/GeostatsGuy/GeostatsPy_Demos/tree/main).\n",
381 | "\n",
382 | "I hope this is helpful,\n",
383 | "\n",
384 | "*Michael*\n",
385 | "\n",
386 | "#### The Author:\n",
387 | "\n",
388 | "### Michael Pyrcz, Professor, The University of Texas at Austin \n",
389 | "*Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*\n",
390 | "\n",
391 | "With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development. \n",
392 | "\n",
393 | "For more about Michael check out these links:\n",
394 | "\n",
395 | "#### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
396 | "\n",
397 | "#### Want to Work Together?\n",
398 | "\n",
399 | "I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.\n",
400 | "\n",
401 | "* Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you! \n",
402 | "\n",
403 | "* Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!\n",
404 | "\n",
405 | "* I can be reached at mpyrcz@austin.utexas.edu.\n",
406 | "\n",
407 | "I'm always happy to discuss,\n",
408 | "\n",
409 | "*Michael*\n",
410 | "\n",
411 | "Michael Pyrcz, Ph.D., P.Eng. Professor, Cockrell School of Engineering and The Jackson School of Geosciences, The University of Texas at Austin\n",
412 | "\n",
413 | "#### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1) \n",
414 | " www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n",
415 | "sGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)\n"
416 | ]
417 | },
418 | {
419 | "cell_type": "code",
420 | "execution_count": null,
421 | "metadata": {},
422 | "outputs": [],
423 | "source": []
424 | }
425 | ],
426 | "metadata": {
427 | "kernelspec": {
428 | "display_name": "Python 3 (ipykernel)",
429 | "language": "python",
430 | "name": "python3"
431 | },
432 | "language_info": {
433 | "codemirror_mode": {
434 | "name": "ipython",
435 | "version": 3
436 | },
437 | "file_extension": ".py",
438 | "mimetype": "text/x-python",
439 | "name": "python",
440 | "nbconvert_exporter": "python",
441 | "pygments_lexer": "ipython3",
442 | "version": "3.11.4"
443 | }
444 | },
445 | "nbformat": 4,
446 | "nbformat_minor": 2
447 | }
448 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Michael Pyrcz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
GeostatsPyDemos: GeostatsPy Python Package for Spatial Data Analytics and Geostatistics Demonstration Workflows Repository (0.0.1)
8 |
9 |
Approximately 40 Well-Documented Spatial Data Analytics and Geostatistics Workflows with the GeostatsPy Package!
10 |
11 | *It is challenging to learn a new Python package. For me, great examples for common workflows are crtical. So I built out over 40 well-documented demonstration workflows that apply GeostatsPy to accomplish common spatial modeling tasks to support my students in my **Data Analytics and Geostatistics**, **Spatial Data Analytics** and **Machine Learning** courses and anyone else learning data analytics and machine learning.*
12 |
13 | ### Michael Pyrcz, Professor, The University of Texas at Austin, Data Analytics, Geostatistics and Machine Learning
14 | #### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)
15 |
16 | ***
17 |
18 | ### Cite As:
19 |
20 | Pyrcz, M.J., 2024, GeostatsPyDemos: GeostatsPy Python Package for Spatial Data Analytics and Geostatistics Demonstration Workflows Repository (0.0.1). Zenodo. https://zenodo.org/doi/10.5281/zenodo.12667035
21 |
22 | [](https://zenodo.org/doi/10.5281/zenodo.12667035)
23 |
24 | ***
25 |
26 | #### Recent Updates
27 |
28 | Here's some highlights from recent updates:
29 |
30 | ##### What's New with Version 0.0.1
31 |
32 | I spent quite a bit of time checking, updating and improving all of the workflows.
33 |
34 | * up to date with GeostatsPy v0.0.68
35 | * improved documentation with concepts and theory from my courses to motivate the workflows
36 | * improved code comments
37 | * improved data and model visualization
38 |
39 | I'm quite happy with the current state. I feel that this set of well-documented workflow for spatial data analytics and geostatistics in Python with GeostatsPy now lives up to its goal - to launch anyone into building spatial data analytics and geostatistics workflow with GeostatsPy! I'm stoked to help out, Michael
40 |
41 | ***
42 |
43 | #### Setup
44 |
45 | A minimum environment includes:
46 |
47 | * Python 3.7.10 - due to the depdendency of GeostatsPy on the Numba package for code acceleration
48 | * GeostatsPy - I am continuously testing these workflow with the most current version, [GeostatsPy](https://pypi.org/project/geostatspy/)(Pyrcz et al., 2021)
49 | * MatPlotLib - plotting
50 | * NumPy - gridded data and array math
51 | * Pandas - tabulated data
52 | * SciPy - statistics module
53 |
54 | The required datasets are available in the [GeoDataSets](https://github.com/GeostatsGuy/GeoDataSets) repository and linked in the workflows.
55 |
56 | #### Repository Summary
57 |
58 | More than 40 well-documented demonstration workflow for common geostatistical workflows with GeostatsPy.
59 |
60 | * utilizing synthetic data from my [GeoDataSets](https://github.com/GeostatsGuy/GeoDataSets) repository
61 | * small and often 2D examples for fast run times and ease of interpretation
62 | * often used and cited in my courses for repeatable educational content
63 |
64 | Common geostatistical workflows that are included:
65 |
66 | * data visualization
67 | * distribution transformation
68 | * spatial data debiasing with descustering
69 | * spatial continuity calculation and modeling with variograms
70 | * spatial estimation
71 | * spatial trend modeling
72 | * spatial simulation
73 | * indicator based estimation and simulation
74 | * cosimulation with secondary data
75 | * summarization over multiple simulated realizations
76 | * spatial model checking
77 | * volume variance relations
78 |
79 | ***
80 |
81 | #### Installing GeostatsPy
82 |
83 | Firstly, if you haven't installed GeostatsPy, here's the GitHub repository [GeostatsPy GitHub](https://github.com/GeostatsGuy/GeostatsPy/tree/master). GeostatsPy is available on the Python Package Index (PyPI) [GeostatsPy PyPI](https://pypi.org/project/geostatspy/).
84 |
85 | To install GeostatsPy, use pip
86 |
87 | ```console
88 | pip install geostatspy
89 | ```
90 | ***
91 |
92 | #### GeostatsPy Package Dependencies
93 |
94 | The functions rely on the following packages:
95 |
96 | 1. **numpy** - for ndarrays
97 | 2. **pandas** - for DataFrames
98 | 3. **numpy.linalg** - for linear algebra
99 | 4. **numba** - for numerical speed up
100 | 5. **scipy** - for fast nearest neighbor search
101 | 6. **matplotlib.pyplot** - for plotting
102 | 7. **tqdm** - for progress bar
103 | 8. **statsmodels** - for weighted (debiased) statistics
104 |
105 | These packages should be available with any modern Python distribution (e.g. https://www.anaconda.com/download/).
106 |
107 | If you get a package import error, you may have to first install some of these packages. This can usually be accomplished by opening up a command window on Windows and then typing 'python -m pip install [package-name]'. More assistance is available with the respective package docs.
108 |
109 | #### GeostatsPyDemos Repository Author:
110 |
111 | ### Michael Pyrcz, Professor, The University of Texas at Austin
112 | *Novel Data Analytics, Geostatistics and Machine Learning Subsurface Solutions*
113 |
114 | With over 17 years of experience in subsurface consulting, research and development, Michael has returned to academia driven by his passion for teaching and enthusiasm for enhancing engineers' and geoscientists' impact in subsurface resource development.
115 |
116 | For more about Michael check out these links:
117 |
118 | #### [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)
119 |
120 | #### Want to Work Together?
121 |
122 | I hope this content is helpful to those that want to learn more about subsurface modeling, data analytics and machine learning. Students and working professionals are welcome to participate.
123 |
124 | * Want to invite me to visit your company for training, mentoring, project review, workflow design and / or consulting? I'd be happy to drop by and work with you!
125 |
126 | * Interested in partnering, supporting my graduate student research or my Subsurface Data Analytics and Machine Learning consortium (co-PIs including Profs. Foster, Torres-Verdin and van Oort)? My research combines data analytics, stochastic modeling and machine learning theory with practice to develop novel methods and workflows to add value. We are solving challenging subsurface problems!
127 |
128 | * I can be reached at mpyrcz@austin.utexas.edu.
129 |
130 | I'm always happy to discuss,
131 |
132 | *Michael*
133 |
134 | Michael Pyrcz, Ph.D., P.Eng. Professor, Cockrell School of Engineering and The Jackson School of Geosciences, The University of Texas at Austin
135 |
136 | #### More Resources Available at: [Twitter](https://twitter.com/geostatsguy) | [GitHub](https://github.com/GeostatsGuy) | [Website](http://michaelpyrcz.com) | [GoogleScholar](https://scholar.google.com/citations?user=QVZ20eQAAAAJ&hl=en&oi=ao) | [Book](https://www.amazon.com/Geostatistical-Reservoir-Modeling-Michael-Pyrcz/dp/0199731446) | [YouTube](https://www.youtube.com/channel/UCLqEr-xV-ceHdXXXrTId5ig) | [LinkedIn](https://www.linkedin.com/in/michael-pyrcz-61a648a1)
137 |
138 | ***
139 |
140 | #### Information about the [GeostatsPy Python Package](https://github.com/GeostatsGuy/GeostatsPy) for Spatial Data Analytics and Geostatistics
141 |
142 | The GeostatsPy Package brings GSLIB: Geostatistical Library (Deutsch and Journel, 1998) functions to Python. GSLIB is a practical and extremely robust set of code for building spatial modeling workflows.
143 |
144 | I created the GeostatsPy Package to support my students in my **Data Analytics**, **Geostatistics** and **Machine Learning** courses. I find my students benefit from hands-on opportunities, in fact it is hard to imagine teaching these topics without providing the opportunity to handle the numerical methods and build workflows. Last year, I tried to have them use the original FORTRAN executables and even with support and worked out examples, it was an uphill battle. In addition, all my students and I are now working in Python for our research. Thus, having access to geostatistical methods in Python directly impacts and facilitates the research of my group. This package retains the spirit of GSLIB:
145 |
146 | * **modularity** - a collection of standalone functions that may be applied in sequence for maximum flexibility for building workflows
147 | * **minimalistic** - the simplest possible code to support the "look at the code" approach to learning
148 | * **fundamental** - based on the well-established geostatistical theory by avoiding ad hoc methods and assumptions
149 |
150 | This package contains 2 parts:
151 |
152 | 1. **geostatspy.geostats** includes GSLIB functions rewritten in Python. This currently includes all the variogram, distribution transformations, and spatial estimation and simulation methods. I will continue adding functions to support modeling operations for practical subsurface model cosntruction.
153 |
154 | 2. **geostatspy.GSLIB** includes reimplimentation of the GSLIB visualizations and low tech wrappers of the numerical methods (note: the low-tech wrapper require access to GSLIB executables).
155 |
156 | #### The GeostatsPy Authors
157 |
158 | The GeostatsPy package is being developed at The University of Texas in the Texas Center for Geostatistics.
159 |
160 | * **Professor Michael J. Pyrcz, Ph.D., P.Eng.** - professor with The University of Texas at Austin. Primary author of the package.
161 |
162 | * **Professor Honggeun Jo** - assistant professor with Inha University, South Korea. Author of 3D subroutines, 3D variogram calculation and modeling and wrapper for sgsim for 3D modeling and more! Thank you, Professor Jo!
163 |
164 | * **Anton Kupenko** - bug fixes, added docstrings, code refractory for PEP8, removed duplicated functions and variables. Thank you, Anton!
165 |
166 | * **Wendi Liu, Ph.D.** - while a Ph.D. student working with Michael Pyrcz at The University of Texas at Austin. Author of 3D subroutines and gammabar method. Also, GSLIB compiles in Mac OSX, and 3D variogram calculation wrapper. Thank you, Dr. Wendi Liu!
167 |
168 | * **Alex E. Gigliotti** - undergraduate student working with Michael Pyrcz at The University of Texas at Austin. Established unit testing. Thank you Alex!
169 |
170 | * **Travis Salomaki** - as an undergraduate student research project with Michael Pyrcz at The University of Texas at Austin. Improving package docs. Thank you, Travis!
171 |
172 | * **Javier Santos, Ph.D.** - while a Ph.D. student working with Michael Pyrcz at The University of Texas at Austin. Author of the post processing algorithm for summarizing over multiple realizations. Thank you, Javier!
173 |
174 | #### Package Inventory
175 |
176 | Here's a list and some details on each of the functions available.
177 |
178 | ##### geostatspy.GSLIB Functions
179 |
180 | Utilities to support moving between Python DataFrames and ndarrays, Data Tables, Gridded Data and Models in Geo-EAS file format (standard to GSLIB):
181 |
182 | 1. **ndarray2GSLIB** - utility to convert 1D or 2D numpy ndarray to a GSLIB Geo-EAS file for use with GSLIB methods
183 | 2. **GSLIB2ndarray** - utility to convert GSLIB Geo-EAS files to a 1D or 2D numpy ndarray for use with Python methods
184 | 3. **Dataframe2GSLIB(data_file,df)** - utility to convert pandas DataFrame to a GSLIB Geo-EAS file for use with GSLIB methods
185 | 4. **GSLIB2Dataframe** - utility to convert GSLIB Geo-EAS files to a pandas DataFrame for use with Python methods
186 | 5. **DataFrame2ndarray** - take spatial data from a DataFrame and make a sparse 2D ndarray (NaN where no data in cell)
187 |
188 | Visualization functions with the same parameterization as GSLIB using matplotlib:
189 |
190 | 6. **pixelplt** - reimplemention in Python of GSLIB pixelplt with matplotlib methods
191 | 7. **pixelplt_st** - reimplemention in Python of GSLIB pixelplt with matplotlib methods with support for sub plots
192 | 8. **pixelplt_log_st** - reimplemention in Python of GSLIB pixelplt with matplotlib methods with support for sub plots and log color bar
193 | 9. **locpix** - pixel plot and location map, reimplementation in Python of a GSLIB MOD with MatPlotLib methods
194 | 10. **locpix_st** - pixel plot and location map, reimplementation in Python of a GSLIB MOD with MatPlotLib methods with support for sub plots
195 | 11. **locpix_log_st** - pixel plot and location map, reimplementation in Python of a GSLIB MOD with MatPlotLib methods with support for sub plots and log color bar
196 | 12. **hist** - histograms reimplemented in Python of GSLIB hist with MatPlotLib methods
197 | 13. **hist_st** - histograms reimplemented in Python of GSLIB hist with MatPlotLib methods with support for sub plots
198 |
199 | Data transformations
200 |
201 | 14. **affine** - affine distribution transformation to correct feature mean and standard deviation
202 | 15. **nscore** - normal score transform, wrapper for nscore from GSLIB (GSLIB's nscore.exe must be in working directory)
203 | 16. **declus** - cell-based declustering, 2D wrapper for declus from GSLIB (GSLIB's declus.exe must be in working directory)
204 |
205 | Spatial Continuity
206 |
207 | 17. **make_variogram** - make a dictionary of variogram parameters to for application with spatial estimation and simulation
208 | 18. **gamv** - irregularly sampled variogram, 2D wrapper for gam from GSLIB (.exe must be in working directory)
209 | 19. **varmap** - regular spaced data, 2D wrapper for varmap from GSLIB (.exe must be in working directory)
210 | 20. **varmapv** - irregular spaced data, 2D wrapper for varmap from GSLIB (.exe must be in working directory)
211 | 21. **vmodel** - variogram model, 2D wrapper for vmodel from GSLIB (.exe must be in working directory)
212 |
213 | Spatial Modeling
214 |
215 | 22. **kb2d** - kriging estimation, 2D wrapper for kb2d from GSLIB (GSLIB's kb2d.exe must be in working directory)
216 | 23. **sgsim_uncond** - sequential Gaussian simulation, 2D unconditional wrapper for sgsim from GSLIB (GSLIB's sgsim.exe must be in working directory)
217 | 24. **sgsim** - sequential Gaussian simulation, 2D and 3D wrapper for sgsim from GSLIB (GSLIB's sgsim.exe must be in working directory)
218 | 25. **cosgsim_uncond** - sequential Gaussian simulation, 2D unconditional wrapper for sgsim from GSLIB (GSLIB's sgsim.exe must be in working directory)
219 |
220 | Spatial Model Resampling
221 |
222 | 26. **sample** - sample 2D model with provided X and Y and append to DataFrame
223 | 27. **gkern** - make a Gaussian kernel for convolution, moving window averaging (from Teddy Hartano, Stack Overflow)
224 | 28. **regular_sample** - extract regular spaced samples from a 2D spatial model
225 | 29. **random_sample** - extract random samples from a 2D spatial model
226 | 30. **DataFrame2ndarray** - convent spatial point data in a DataFrame to a sparse ndarray grid
227 |
228 | ##### geostatspy.geostats Functions
229 |
230 | Numerical methods in GSLIB (Deutsch and Journel, 1998) translated to Python:
231 |
232 | 31. **correct_trend** - correct the order relations of an indicator-based trend model
233 | 32. **backtr** - GSLIB's backtr function to transform a distribution
234 | 33. **declus** - GSLIB's DECLUS program reimplimented for cell-based declustering in 2D
235 | 34. **gam** - GSLIB's GAM program reimplimented for variogram calculation with regular data in 2D
236 | 35. **gamv** - GSLIB's GAMV program reimplimented for variogram calculation with iregular data in 2D
237 | 36. **varmapv** - GSLIB's VARMAP program reimplimented for irregularly spaced spatial data in 2D
238 | 37. **vmodel** - GSLIB's VMODEL program reimplimented for visualization of nested variogram models in 2D
239 | 38. **nscore** - GSLIB's NSCORE program reimplimented for normal score distribution transformation
240 | 39. **kb2d** - GSLIB's KB2D program reimplimented for 2D kriging-based spatial estimation
241 | 40. **ik2d** - GSLIB's IK3D program reimplimented for 2D indicator-based kriging estimation
242 | 41. **kb3d** - GSLIB's kt3d program reimplimented for 3D kriging-based spatial kriging estimation
243 | 42. **sgsim** - GSLIB's sgsim program reimplimented for 2D spatial simulation
244 | 43. **postsim** - GSLIB's postsim program reimplimented for summarizing over multiple realizations
245 |
246 | More functionality will be added soon.
247 |
--------------------------------------------------------------------------------