Todos los productos
Search
Centro de documentos

MaxCompute:API de MapReduce

Última actualización: Sep 03, 2026

La API de MapReduce de PyODPS DataFrame permite escribir lógica personalizada de map y reduce en Python para ejecutarla a gran escala en MaxCompute. Un trabajo de map_reduce puede contener solo mappers, solo reducers o ambos.

Ejemplo de WordCount

El siguiente ejemplo cuenta las apariciones de palabras en una tabla que contiene una sola columna STRING.

#encoding=utf-8
from odps import ODPS
from odps import options
from odps.df import DataFrame

options.verbose = True

o = ODPS('your-access-id', 'your-secret-access-key',
         project='DMP_UC_dev',
         endpoint='http://service-corp.odps.aliyun-inc.com/api')

def mapper(row):
    for word in row[0].split():
        yield word.lower(), 1

def reducer(keys):
    # Use a list instead of cnt=0. A plain integer would be treated as a local
    # variable inside h(), so its value would not appear in the output.
    cnt = [0]
    def h(row, done):  # done=True when all rows for this key have been processed
        cnt[0] += row[1]
        if done:
            yield keys[0], cnt[0]
    return h

word_count = DataFrame(o.get_table('zx_word_count'))
table = word_count.map_reduce(
    mapper, reducer,
    group=['word'],
    mapper_output_names=['word', 'cnt'],
    mapper_output_types=['string', 'int'],
    reducer_output_names=['word', 'cnt'],
    reducer_output_types=['string', 'int'],
)

Salida esperada:

     word  cnt
0     are    1
1     day    1
2  doing?    1
...

El parámetro group especifica el campo que utiliza el reducer para agrupar las filas entrantes. Si se omite, se usan todos los campos para la agrupación. El reducer recibe las keys agregadas y procesa cada fila que comparte dichas claves. La bandera done es True cuando se ha procesado la última fila correspondiente a una clave determinada.

Es posible escribir el reducer como una clase invocable en lugar de usar un cierre:

class reducer(object):
    def __init__(self, keys):
        self.cnt = 0

    def __call__(self, row, done):  # done=True when all rows for this key are processed
        self.cnt += row.cnt
        if done:
            yield row.word, self.cnt

Simplificación del esquema de salida con el decorador @output

Utilice el decorador @output para declarar los nombres y tipos de los campos de salida directamente en la función. Esto elimina la necesidad de pasar mapper_output_names, mapper_output_types, reducer_output_names y reducer_output_types a map_reduce.

from odps.df import output

@output(['word', 'cnt'], ['string', 'int'])
def mapper(row):
    for word in row[0].split():
        yield word.lower(), 1

@output(['word', 'cnt'], ['string', 'int'])
def reducer(keys):
    cnt = [0]
    def h(row, done):
        cnt[0] += row.cnt
        if done:
            yield keys.word, cnt[0]
    return h

word_count = DataFrame(o.get_table('zx_word_count'))
table = word_count.map_reduce(mapper, reducer, group='word')

Para ordenar las filas dentro de cada grupo durante la iteración, pase el parámetro sort. El parámetro ascending controla la dirección de la ordenación: un solo valor booleano aplica el mismo orden a todos los campos de sort; una lista permite establecer una dirección diferente por campo (la longitud de la lista debe coincidir con el número de campos de sort).

Especificación de un combiner

Durante un trabajo de MapReduce, la salida del mapper se transfiere a través de la red hacia los reducers, en un paso denominado shuffle. Este proceso implica E/S de disco, serialización de datos y transferencia de red, por lo que constituye una de las partes más costosas del pipeline.

Un combiner reduce el costo del shuffle al agregar localmente la salida del mapper en cada nodo antes de enviarla a los reducers. Esta estrategia resulta más eficaz para operaciones conmutativas y asociativas, como conteos y sumas.

Restricciones previas a la escritura de un combiner:

  • Un combiner no puede hacer referencia a recursos.

  • Los nombres y tipos de los campos de salida deben coincidir exactamente con los del mapper correspondiente.

La interfaz del combiner es idéntica a la del reducer. Pase la función del combiner mediante el parámetro combiner:

words_df.map_reduce(mapper, reducer, combiner=reducer, group='word')

Referencia a recursos

Tanto los mappers como los reducers pueden referenciar su propio conjunto de recursos. Los recursos se pasan como parámetros de la función (por ejemplo, def mapper(resources):).

El siguiente ejemplo filtra palabras vacías en el mapper e incrementa en 5 el conteo de las palabras incluidas en una lista blanca dentro del reducer:

white_list_file = o.create_resource('pyodps_white_list_words', 'file', file_obj='Python\nWorld')

@output(['word', 'cnt'], ['string', 'int'])
def mapper(resources):
    stop_words = set(r[0].strip() for r in resources[0])
    def h(row):
        for word in row[0].split():
            if word not in stop_words:
                yield word, 1
    return h

@output(['word', 'cnt'], ['string', 'int'])
def reducer(resources):
    d = dict()
    d['white_list'] = set(word.strip() for word in resources[0])
    d['cnt'] = 0
    def inner(keys):
        d['cnt'] = 0
        def h(row, done):
            d['cnt'] += row.cnt
            if done:
                if row.word in d['white_list']:
                    d['cnt'] += 5
                yield keys.word, d['cnt']
        return h
    return inner

words_df.map_reduce(mapper, reducer, group='word',
                    mapper_resources=[stop_words],
                    reducer_resources=[white_list_file])

Salida esperada:

    word  cnt
0  hello    2
1   life    1
2  python    7
3   world    6
4   short    1
5     use    1

Uso de bibliotecas de Python de terceros

Importante

Las características de bytecode de Python 3, como yield from, provocan errores en los workers de MaxCompute que ejecutan Python 2.7. Pruebe su código de extremo a extremo antes de ejecutar trabajos de MapReduce basados en Python 3 en producción.

Especifique las bibliotecas de forma global para aplicarlas a todas las llamadas de map_reduce en una sesión:

from odps import options
options.df.libraries = ['six.whl', 'python_dateutil.whl']

O bien páselas a una ejecución individual:

df.map_reduce(mapper=my_mapper, reducer=my_reducer, group='key').execute(
    libraries=['six.whl', 'python_dateutil.whl']
)

Reorganización de datos

Cuando los datos se distribuyen de manera desigual entre las particiones del clúster, llame a reshuffle para reequilibrarlos. De forma predeterminada, las filas se asignan a las particiones mediante un hash aleatorio:

df1 = df.reshuffle()

Para distribuir por una columna específica y ordenar el resultado:

df1 = df.reshuffle('name', sort='id', ascending=False)

Filtro Bloom

bloom_filter prefiltra rápidamente un conjunto de datos frente a otro antes de realizar una unión, lo que reduce la cantidad de filas que requieren shuffle y comparación. Funciona mejor cuando un conjunto de datos es mucho mayor que el otro; por ejemplo, al filtrar datos de eventos de navegación contra un conjunto menor de registros de transacciones antes de unirlos.

El filtro es aproximado: elimina las filas que definitivamente están ausentes del conjunto de referencia, pero puede conservar un pequeño número de filas que en realidad no están presentes.

df1 = DataFrame(pd.DataFrame({'a': ['name1', 'name2', 'name3', 'name1'], 'b': [1, 2, 3, 4]}))
df2 = DataFrame(pd.DataFrame({'a': ['name1']}))

df1.bloom_filter('a', df2.a)
# The first argument can also be a column expression, e.g., df1.a + '1'

Salida esperada:

       a  b
0  name1  1
1  name1  4

En este ejemplo, name2 y name3 se filtran. En conjuntos de datos más grandes, es posible que el filtro no elimine todas las filas sin coincidencia, pero el resultado de la unión sigue siendo correcto, ya que el filtro solo afecta el rendimiento y no la precisión.

Configure el filtro con estos parámetros:

Parameter

Default

Effect on accuracy

Effect on memory

capacity

3000

Valores más altos reducen los falsos positivos

Aumenta

error_rate

0.01

Valores más bajos reducen los falsos positivos

Aumenta

Establezca ambos parámetros según el tamaño de su conjunto de datos y la memoria disponible.

Para obtener información sobre la ejecución de operaciones de DataFrame, consulte DataFrame execution.

Tabla dinámica

pivot_table resume los datos mediante la agrupación de filas y el cálculo de valores agregados en las columnas.

Datos de muestra:

>>> df
     A    B      C  D  E
0  foo  one  small  1  3
1  foo  one  large  2  4
2  foo  one  large  2  5
3  foo  two  small  3  6
4  foo  two  small  3  4
5  bar  one  large  4  5
6  bar  one  small  5  3
7  bar  two  small  6  2
8  bar  two  large  7  1

El parámetro rows es obligatorio. Especifica los campos por los cuales agrupar; la función de agregación predeterminada es mean:

>>> df['A', 'D', 'E'].pivot_table(rows='A')
     A  D_mean  E_mean
0  bar     5.5    2.75
1  foo     2.2    4.40

Pase varios campos a rows para lograr una agrupación más detallada:

>>> df.pivot_table(rows=['A', 'B', 'C'])
     A    B      C  D_mean  E_mean
0  bar  one  large     4.0     5.0
1  bar  one  small     5.0     3.0
...

Use values para restringir las columnas que se van a agregar:

>>> df.pivot_table(rows=['A', 'B'], values='D')
     A    B    D_mean
0  bar  one  4.500000
1  bar  two  6.500000
2  foo  one  1.666667
3  foo  two  3.000000

Use aggfunc para aplicar una o más funciones de agregación:

>>> df.pivot_table(rows=['A', 'B'], values=['D'], aggfunc=['mean', 'count', 'sum'])
     A    B    D_mean  D_count  D_sum
0  bar  one  4.500000        2      9
1  bar  two  6.500000        2     13
2  foo  one  1.666667        3      5
3  foo  two  3.000000        2      6

Use columns para convertir los valores de una columna en nuevos encabezados de columna:

>>> df.pivot_table(rows=['A', 'B'], values='D', columns='C')
     A    B  large_D_mean  small_D_mean
0  bar  one           4.0           5.0
1  bar  two           7.0           6.0
2  foo  one           2.0           1.0
3  foo  two           NaN           3.0

Use fill_value para reemplazar NaN con un valor predeterminado:

>>> df.pivot_table(rows=['A', 'B'], values='D', columns='C', fill_value=0)
     A    B  large_D_mean  small_D_mean
0  bar  one             4             5
1  bar  two             7             6
2  foo  one             2             1
3  foo  two             0             3

Conversión de cadenas clave-valor

DataFrame puede analizar cadenas clave-valor para separarlas en columnas distintas y convertir datos tabulares nuevamente en cadenas clave-valor. Para obtener información sobre la creación de objetos DataFrame, consulte Create a DataFrame object.

Extracción de pares clave-valor en columnas

Utilice extract_kv para analizar una columna que contiene cadenas clave-valor delimitadas:

>>> df
    name               kv
0  name1  k1=1,k2=3,k5=10
1  name1    k1=7.1,k7=8.2
2  name2    k2=1.2,k3=1.5
3  name2      k9=1.1,k2=1

>>> df.extract_kv(columns=['kv'], kv_delim='=', item_delim=',')
   name   kv_k1  kv_k2  kv_k3  kv_k5  kv_k7  kv_k9
0  name1    1.0    3.0    NaN   10.0    NaN    NaN
1  name1    7.0    NaN    NaN    NaN    8.2    NaN
2  name2    NaN    1.2    1.5    NaN    NaN    NaN
3  name2    NaN    1.0    NaN    NaN    NaN    1.1

Parámetros:

Parameter

Description

Default

columns

Campos de los cuales extraer pares clave-valor

—

kv_delim

Delimitador entre cada clave y su valor

:

item_delim

Delimitador entre pares clave-valor

,

Los nombres de las columnas de salida siguen el patrón {original_field}_{key}, unidos con un guion bajo. Los valores faltantes toman NaN como predeterminado. Use fill_value para sustituir los valores faltantes por un valor predeterminado específico:

>>> df.extract_kv(columns=['kv'], kv_delim='=', fill_value=0)
   name   kv_k1  kv_k2  kv_k3  kv_k5  kv_k7  kv_k9
0  name1    1.0    3.0    0.0   10.0    0.0    0.0
1  name1    7.0    0.0    0.0    0.0    8.2    0.0
2  name2    0.0    1.2    1.5    0.0    0.0    0.0
3  name2    0.0    1.0    0.0    0.0    0.0    1.1

Conversión de columnas a cadenas clave-valor

Utilice to_kv para serializar varias columnas en una sola columna de cadena clave-valor:

>>> df
   name    k1   k2   k3    k5   k7   k9
0  name1  1.0  3.0  NaN  10.0  NaN  NaN
1  name1  7.0  NaN  NaN   NaN  8.2  NaN
2  name2  NaN  1.2  1.5   NaN  NaN  NaN
3  name2  NaN  1.0  NaN   NaN  NaN  1.1

>>> df.to_kv(columns=['k1', 'k2', 'k3', 'k5', 'k7', 'k9'], kv_delim='=')
    name               kv
0  name1  k1=1,k2=3,k5=10
1  name1    k1=7.1,k7=8.2
2  name2    k2=1.2,k3=1.5
3  name2      k9=1.1,k2=1