RQI y FZI en Python: Líneas Promedio para Agrupar Datos de Núcleos

Porosidad, permeabilidad y grupos exploratorios de calidad de flujo

Yacimientos
Petrofísica
Python
Calcula RQI, porosidad normalizada y FZI con datos de núcleos. Agrupa valores de log(FZI), obtiene líneas de FZI promedio y visualiza su relación con la porosidad y la permeabilidad en Python.
Autor/a
Fecha de publicación

3 de octubre de 2026

¿Cómo obtener líneas de FZI promedio?

Dos muestras con porosidad similar pueden tener permeabilidades muy distintas. Los índices RQI y FZI permiten analizar esa variación y organizar muestras con características de flujo semejantes.

Seguiremos el procedimiento de unidades hidráulicas de flujo del capítulo 4 de Reservoir Engineering Handbook, quinta edición, de Tarek Ahmed: calcular los índices, representar RQI contra porosidad normalizada y trazar líneas de FZI característico. Para generar grupos de forma reproducible añadiremos una agrupación numérica sencilla en Python.

Trabajaremos con 406 muestras de núcleos de dos pozos, identificados como WELL_1 y WELL_2. La identificación de grupos será exploratoria: el número de unidades hidráulicas de flujo debe contrastarse con información geológica y petrofísica.

Visita el canal @rigopetrodata

Tutoriales de R, Python y Excel para ingeniería petrolera

Ver canal

Las tres ecuaciones

El índice de calidad del yacimiento se calcula como:

\[ RQI=0.0314\sqrt{\frac{k}{\phi}} \]

La relación entre volumen de poros y volumen de granos es:

\[ \phi_z=\frac{\phi}{1-\phi} \]

Y el indicador de zona de flujo es:

\[ FZI=\frac{RQI}{\phi_z} \]

Usamos k en mD y φ como fracción. Con esas unidades, RQI y FZI se expresan en µm y φz es adimensional. RQI es un índice asociado a la geometría porosa, no una medición directa del radio de garganta de cada muestra.

Al reorganizar la última ecuación:

\[ RQI=FZI\,\phi_z \]

\[ \log_{10}(RQI)=\log_{10}(\phi_z)+\log_{10}(FZI) \]

Por ello, un FZI constante genera una recta de pendiente uno en coordenadas logarítmicas. Diferentes valores generan rectas paralelas. Su inclinación visual solo será de 45° si ambos ejes tienen la misma escala gráfica por década.

1. Cargar los datos

Descargar el dataset de núcleos

Guarda el CSV en la carpeta de tu cuaderno. Contiene las columnas WELL, MD, POROSITY y PERMEABILITY. Para este cálculo utilizaremos la porosidad, ya expresada como fracción, y la permeabilidad en mD. Conservamos el identificador de pozo y la profundidad para posteriores revisiones.

Necesitamos NumPy, pandas, Matplotlib y SciPy. Puedes instalarlos con pip install numpy pandas matplotlib scipy en la terminal. Ejecuta los fragmentos en orden.

Ver código
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.cluster.vq import kmeans2

datos = pd.read_csv("Core_data.csv")
validos = (datos["POROSITY"].between(0, 1, inclusive="neither")
           & (datos["PERMEABILITY"] > 0)
           & np.isfinite(datos["PERMEABILITY"]))
print(f"Muestras válidas: {validos.sum()} de {len(datos)}")
datos = datos.loc[validos].copy()
datos.head()
Muestras válidas: 406 de 406
WELL MD POROSITY PERMEABILITY
0 WELL_1 4038.350098 0.201 1973.9240
1 WELL_1 4038.850098 0.203 1158.9270
2 WELL_1 4039.350098 0.207 1935.1169
3 WELL_1 4039.850098 0.206 1003.6810
4 WELL_1 4040.350098 0.190 901.4980

Las 406 muestras son válidas para las ecuaciones. Los valores de porosidad van de 0.017 a 0.282 y la permeabilidad de 0.002 a 15,913.97 mD. Un valor cero o negativo no puede utilizarse en este análisis logarítmico.

2. Calcular RQI, φz y FZI

Ver código
phi = datos["POROSITY"]
k = datos["PERMEABILITY"]

datos["RQI"] = 0.0314 * np.sqrt(k / phi)
datos["PHIZ"] = phi / (1 - phi)
datos["FZI"] = datos["RQI"] / datos["PHIZ"]
datos["logFZI"] = np.log10(datos["FZI"])

datos[["WELL", "POROSITY", "PERMEABILITY", "RQI", "PHIZ", "FZI"]].head().round(4)
WELL POROSITY PERMEABILITY RQI PHIZ FZI
0 WELL_1 0.201 1973.9240 3.1117 0.2516 12.3694
1 WELL_1 0.203 1158.9270 2.3725 0.2547 9.3148
2 WELL_1 0.207 1935.1169 3.0360 0.2610 11.6306
3 WELL_1 0.206 1003.6810 2.1918 0.2594 8.4479
4 WELL_1 0.190 901.4980 2.1629 0.2346 9.2208

3. Observar la distribución de las muestras

Antes de agrupar, representamos los puntos por pozo. Ambos ejes son logarítmicos.

Ver código
plt.figure(figsize=(8, 5))
for pozo, grupo in datos.groupby("WELL"):
    plt.scatter(grupo["PHIZ"], grupo["RQI"], s=18, alpha=0.6, label=pozo)
plt.xscale("log")
plt.yscale("log")
plt.xlabel("Porosidad normalizada, φz")
plt.ylabel("RQI (µm)")
plt.grid(alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()
Figura 1: Distribución de las muestras de los dos pozos en el espacio RQI–φz.

Una muestra situada más arriba que otra con el mismo φz tiene mayor RQI y FZI. La dispersión ayuda a visualizar la variabilidad, pero no determina por sí sola cuántas unidades de flujo existen.

4. Crear cinco grupos exploratorios

Para este primer ejercicio elegimos cinco grupos y aplicamos k-means únicamente a log10(FZI). Es una decisión de trabajo que podemos modificar en n_grupos, no un número de unidades demostrado por los datos ni una regla del método.

Agrupar en log(FZI) utiliza diferencias relativas y es coherente con la gráfica log–log. No incorporamos el pozo o la profundidad al algoritmo. Esta etapa numérica complementa el procedimiento gráfico del libro.

Ver código
n_grupos = 5
valores = datos[["logFZI"]].to_numpy()
centros, etiquetas = kmeans2(valores, n_grupos, iter=100, minit="++", seed=42)

orden = np.argsort(centros.ravel())
numero = np.empty(n_grupos, dtype=int)
numero[orden] = np.arange(1, n_grupos + 1)
datos["Grupo"] = numero[etiquetas]

Numeramos los grupos de menor a mayor FZI. La semilla fija hace reproducible esta inicialización. K-means impone el número solicitado y puede depender de la inicialización: sus etiquetas son candidatos para revisión, no una validación de HFU.

5. Obtener el FZI promedio de cada grupo

Para ajustar una línea de pendiente uno en escala logarítmica calculamos:

\[ b_j=\operatorname{promedio}\left[\log_{10}(RQI_i)-\log_{10}(\phi_{z,i})\right] =\operatorname{promedio}\left[\log_{10}(FZI_i)\right] \]

\[ FZI_{\mathrm{prom},j}=10^{b_j} \]

Este resultado es la media geométrica, que minimiza la suma de residuos cuadrados verticales en log(RQI) con pendiente fija igual a uno y el mismo peso para cada muestra. No es la media aritmética. Esta elección concreta cómo calcularemos el promedio para las líneas del ejemplo.

Ver código
resumen = datos.groupby("Grupo").agg(
    Muestras=("FZI", "size"),
    FZI_min=("FZI", "min"),
    logFZI_medio=("logFZI", "mean"),
    FZI_max=("FZI", "max")
)
resumen["FZI_prom"] = 10**resumen["logFZI_medio"]
resumen[["Muestras", "FZI_min", "FZI_prom", "FZI_max"]].round(3)
Muestras FZI_min FZI_prom FZI_max
Grupo
1 34 0.122 0.318 0.591
2 104 0.620 1.155 1.653
3 149 1.671 2.372 4.112
4 77 4.276 7.259 10.569
5 42 10.708 15.565 30.379

Con esta configuración obtenemos:

Grupo Muestras FZI promedio geométrico (µm)
1 34 0.318
2 104 1.155
3 149 2.372
4 77 7.259
5 42 15.565

Las cinco líneas tendrán pendiente uno. Si ajustáramos también una pendiente libre para cada grupo, ya no representarían un FZI constante.

🛒
Propiedades De Los Fluidos Del Yacimiento

Disponible en Mercado Libre

Ver oferta →

6. Dibujar las líneas de FZI promedio

Cada grupo comparte color con su línea. Extendemos las líneas hasta φz = 1, donde RQI toma el valor del FZI promedio. Esa extensión permite leer la intersección y no implica que existan muestras hasta ese punto.

Ver código
x = np.geomspace(datos["PHIZ"].min(), 1, 200)
plt.figure(figsize=(8, 6))
for grupo, fila in resumen.iterrows():
    puntos = datos[datos["Grupo"] == grupo]
    color = f"C{grupo - 1}"
    fzi = fila["FZI_prom"]
    plt.scatter(puntos["PHIZ"], puntos["RQI"], s=18, alpha=0.5, color=color)
    plt.plot(x, fzi * x, color=color, label=f"G{grupo}: FZI = {fzi:.2f} µm")
plt.xscale("log")
plt.yscale("log")
plt.axvline(1, color="gray", linestyle=":")
plt.xlabel("Porosidad normalizada, φz")
plt.ylabel("RQI (µm)")
plt.grid(alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()
Figura 2: Muestras agrupadas y líneas de FZI promedio geométrico. Las líneas se extienden hasta φz = 1 para visualizar su intersección.

Los puntos no caen exactamente sobre las líneas porque cada grupo contiene un intervalo de FZI. La línea resume su posición central en escala logarítmica.

La agrupación usa el propio FZI, por lo que la cercanía de los puntos a las líneas es parte del criterio de clasificación. No debe interpretarse como una prueba independiente de que hay cinco unidades hidráulicas.

7. Visualizar las curvas equivalentes de permeabilidad

Podemos regresar al espacio porosidad–permeabilidad despejando k:

\[ k=\left(\frac{FZI_{\mathrm{prom}}}{0.0314}\right)^2 \frac{\phi^3}{(1-\phi)^2} \]

El libro expresa el coeficiente como aproximadamente 1014. Aquí conservamos \(1/0.0314^2\) para mantener la consistencia con el cálculo inicial de RQI. En este gráfico semilogarítmico aparecen curvas, aunque en RQI–φz las líneas sean rectas.

Ver código
porosidad = np.linspace(datos["POROSITY"].min(), datos["POROSITY"].max(), 200)
plt.figure(figsize=(8, 6))
for grupo, fila in resumen.iterrows():
    puntos = datos[datos["Grupo"] == grupo]
    color = f"C{grupo - 1}"
    fzi = fila["FZI_prom"]
    permeabilidad = (fzi / 0.0314)**2 * porosidad**3 / (1 - porosidad)**2
    plt.scatter(puntos["POROSITY"], puntos["PERMEABILITY"], s=18, alpha=0.5, color=color)
    plt.plot(porosidad, permeabilidad, color=color, label=f"G{grupo}: FZI = {fzi:.2f} µm")
plt.yscale("log")
plt.xlabel("Porosidad, φ (fracción)")
plt.ylabel("Permeabilidad (mD)")
plt.grid(alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()
Figura 3: Curvas de permeabilidad para los mismos cinco valores de FZI promedio, sobre los datos de núcleos.

Interpretación y siguiente paso

El grupo 1 tiene el menor FZI característico y el grupo 5, el mayor. A una misma porosidad, un FZI mayor corresponde a mayor permeabilidad. Esto describe calidad de flujo dentro del modelo, no asigna automáticamente una litología.

Para evaluar si los grupos representan HFU, revisa su distribución por pozo y profundidad y su relación con facies, textura y mediciones de presión capilar. También conviene comparar diferentes números de grupos e inicializaciones y comprobar si las conclusiones se mantienen.

La permeabilidad calculada con el FZI individual reproduce la entrada por identidad algebraica. La curva promedio resume cada grupo; para usarla como predictor en otras muestras se necesita validar la clasificación y la estimación con datos independientes.

Referencia

Ahmed, T. Reservoir Engineering Handbook, quinta edición. Capítulo 4, Fundamentals of Rock Properties, sección Hydraulic Flow Units (HFU), ecuaciones 4-91 a 4-94 y ejemplo 4-32, páginas impresas 269–274.