ampl / amplpy

Python API for AMPL
https://amplpy.ampl.com
BSD 3-Clause "New" or "Revised" License
64 stars 19 forks source link

Finding no way of setting data for a param x{someSet, 1..N}; #17

Closed Xtreme-G closed 5 years ago

Xtreme-G commented 5 years ago

Setting through a pandas Dataframe fails due to:

~/anaconda3/envs/py36/lib/python3.6/site-packages/amplpy/dataframe.py in __init__(self, index, columns, **kwargs)
    110                 len(index_names),
    111                 list(index_names) + list(column_names),
--> 112                 len(index_names) + len(column_names)
    113             )
    114             for col in index:

TypeError: list must contain strings

Setting through an AMPL dataframe created from numpy fails due do:

~/anaconda3/envs/py36/lib/python3.6/site-packages/amplpy/entity.py in setValues(self, data)
    197         """
    198         if isinstance(data, DataFrame):
--> 199             self._impl.setValuesDf(data._impl)
    200         else:
    201             if pd is not None and isinstance(data, pd.DataFrame):

TypeError: Dataframe must have exactly one data column.
fdabrandao commented 5 years ago

From the first error message it looks like you are trying to assign data from a pandas.DataFrame without setting column names. By specifying the column name with columns=['value'] it should work. From the second error message it looks like you specified only one column as index and two columns as data.

The following example works with amplpy v0.6.1 and shows how to assign data from a pandas.DataFrame and from an amplpy.DataFrame:

from amplpy import AMPL, DataFrame
import pandas as pd

ampl = AMPL()
ampl.eval('''
set someSet := 1..100;
param N := 5;
param x{someSet, 1..N} default 0;
''')

x_values = {
  (1, 1): 3,
  (6, 3): -1,
  (99, 5): 15,
}

df = pd.DataFrame.from_dict(x_values, orient='index', columns=['value'])
print('df:', df)

ampl.param['x'] = df
ampl.display('x')

df1 = DataFrame(('i0', 'i1'), ('value'))
df1.addRow(30, 3, 23)
df1.addRow(25, 1, 9)

ampl.param['x'] = df1
ampl.display('x')
Xtreme-G commented 5 years ago

I was a little vague on what I was trying to do exactly. It was to index the dataframe by my set and use integer columns for 1..N. This did not work, but your answer led me in the right direction.

from amplpy import AMPL, DataFrame
import pandas as pd

ampl = AMPL()
ampl.eval('''
set someSet := {"First", "Second"};
param N := 5;
param x{someSet, 1..N} default 0;
''')

df = pd.DataFrame([[1,2,3,4,5], [6,7,8,9,0]], index=['First', 'Second'], columns=[1,2,3,4,5])
print('df:', df)

ampl.param['x'] = pd.DataFrame.from_dict(df.stack().to_dict(), orient='index', columns=['v'])
ampl.display('x')

Thank you!

fdabrandao commented 5 years ago

Just one minor note: you could also use ampl.param['x'] = df.stack().to_dict() to perform the assignment directly from a dictionary so that you don't need to create a new Pandas DataFrame. Moreover, we are planning to convert numeric column names into string column names automatically in the next bugfix release so that you would be able to do the following ampl.param['x'] = df.stack() (still not valid at the moment).

fdabrandao commented 5 years ago

If you install amplpy v0.6.2b0 (pip install amplpy --upgrade --pre), you should now be able to do the following:

from amplpy import AMPL, DataFrame
import pandas as pd

ampl = AMPL()
ampl.eval('''
set someSet := {"First", "Second"};
param N := 5;
param x1{someSet, 1..N} default 0;
param x2{someSet, 1..N} default 0;
param x3{someSet, 1..N} default 0;
param x4{someSet, 1..N} default 0;
''')

df = pd.DataFrame([
        [1, 2, 3, 4, 5],
        [6, 7, 8, 9, 0]
    ],
    index=['First', 'Second'],
    columns=[1, 2, 3, 4, 5]
)
ampl.param['x1'] = pd.DataFrame.from_dict(
    df.stack().to_dict(),
    orient='index',
    columns=['v']
)
ampl.param['x2'] = pd.DataFrame(df.stack())
ampl.param['x3'] = df.stack().to_dict()
ampl.param['x4'] = df.stack()
d1 = ampl.param['x1'].getValues().toDict()
d2 = ampl.param['x2'].getValues().toDict()
d3 = ampl.param['x3'].getValues().toDict()
d4 = ampl.param['x4'].getValues().toDict()
print(d1)
print(d2)
print(d3)
print(d4)
assert d1 == d2 == d3 == d4