Skip to main content

Fuzzy Cognitive Maps for Behavior Change Interventions and Evaluation

Project description

FCMpy: A package for Constructing and Analysing Fuzzy Cognitive Maps in Python.

The fcmpy is Python package for automatically generating causal weights for fuzzy cognitive maps based on qualitative inputs (by using fuzzy logic), optimizing the FCM connection matrix via Machine Learning Algorithms and testing what-if scenarios. The package includes the following submodules:

  • ExpertFcm
  • Simulation
  • Intervention
  • ---> (ML TBA)

The ExpertFcm module includes methods for deriving causal weights of an FCM based on qualitative data.
The FcmSimulator module provides methods for running simulations on top of a given FCM structure.
The FcmIntervention module allows testing what-if scenarios on top of the specified FCMs.

Installation

FCMpy requires python >=3.8.1 and depends on:

  • pandas>=1.0.3
  • numpy>=numpy==1.18.2
  • scikit-fuzzy>=0.4.2
  • tqdm>=4.50.2
  • openpyxl

and will soon be available on PyPi! The latest version can be installed by:

pip install fcmpy

Alternatively, you can install it from source or develop this package, you can fork and clone this repository then install FCMpy by running:

py -m pip install --user --upgrade setuptools wheel
py setup.py sdist bdist_wheel
py -m pip install install e . 

You can run the unittest for the package as follows:

py -m unittest discover unittests

Examples

Below we present the fast implementation of the library. More specifically, how to construct an FCM based on the supplied data using Fuzzy Logic, run simulations on a defined FCMs and test interventions on top of the defined FCM structure.

Building FCMs based on qualitative inputs using Fuzzy Logic

Step 1: Generate Fuzzy Membership Functions

Here we generate triangular fuzzy membership functions for 11 linguistic terms.

from fcmpy import ExpertFcm, FcmSimulator, FcmIntervention 

fcm = ExpertFcm()

fcm.linguistic_terms = {
                        '-VH': [-1, -1, -0.75],
                        '-H': [-1, -0.75, -0.50],
                        '-M': [-0.75, -0.5, -0.25], 
                        '-L': [-0.5, -0.25, 0],
                        '-VL': [-0.25, 0, 0],
                        'NA': [-0.001, 0, 0.001],
                        '+VL': [0, 0, 0.25],
                        '+L': [0, 0.25, 0.50],
                        '+M': [0.25, 0.5, 0.75],
                        '+H': [0.5, 0.75, 1],
                        '+VH': [0.75, 1, 1]
                        }

fcm.universe = np.arange(-1, 1.05, .05)

fcm.fuzzy_membership = fcm.automf(method='trimf')

Let's take a look at the generated membership functions.

mfs = fcm.fuzzy_membership

fig = plt.figure(figsize= (10, 5))
axes = plt.axes()

for i in mfs:
    axes.plot(fcm.universe, mfs[i], linewidth=0.4, label=str(i))
    axes.fill_between(fcm.universe, mfs[i], alpha=0.5)

axes.legend(bbox_to_anchor=(0.95, 0.6))

axes.spines['top'].set_visible(False)
axes.spines['right'].set_visible(False)
axes.get_xaxis().tick_bottom()
axes.get_yaxis().tick_left()
plt.tight_layout()

png

Step 2: Build FCMs based on qualitative input data using Fuzzy Logic

  • Read data from a csv file.
data = fcm.read_data(file_path= os.path.abspath('../unittests/test_cases/data_test.csv'), 
                      sep_concept='->', csv_sep=';')
Output[1]

OrderedDict([('Expert0',
                 -vh  -h  -m  -l  -vl  na  +vl  +l  +m  +h  +vh From  To
              0    1   0   0   0    0   0    0   0   0   0    0   C1  C2
              1    0   0   0   0    0   0    0   0   0   1    0   C2  C1
              2    0   0   0   0    0   0    0   0   0   1    0   C3  C1
              3    0   0   0   0    0   0    1   0   0   0    0   C3  C4),
             ('Expert1',
                 -vh  -h  -m  -l  -vl  na  +vl  +l  +m  +h  +vh From  To
              0    1   0   0   0    0   0    0   0   0   0    0   C1  C2
              1    0   0   0   0    0   0    0   0   0   0    1   C2  C1
              2    0   0   0   0    0   0    0   0   1   0    0   C3  C1
              3    0   0   0   0    0   0    0   1   0   0    0   C3  C4),
             ('Expert2',
                 -vh  -h  -m  -l  -vl  na  +vl  +l  +m  +h  +vh From  To
              0    0   1   0   0    0   0    0   0   0   0    0   C1  C2
              1    0   0   0   0    0   0    0   0   0   1    0   C2  C1
              2    0   0   0   0    0   0    0   0   1   0    0   C3  C1
              3    0   0   0   0    0   0    1   0   0   0    0   C3  C4),
             ('Expert3',
                 -vh  -h  -m  -l  -vl  na  +vl  +l  +m  +h  +vh From  To
              0    0   1   0   0    0   0    0   0   0   0    0   C1  C2
              1    0   0   0   0    0   0    0   0   1   0    0   C2  C1
              2    0   0   0   0    0   0    0   0   1   0    0   C3  C1
              3    0   0   0   0    0   0    1   0   0   0    0   C3  C4),
             ('Expert4',
                 -vh  -h  -m  -l  -vl  na  +vl  +l  +m  +h  +vh From  To  no causality
              0    0   1   0   0    0   0    0   0   0   0    0   C1  C2           0.0
              1    0   0   0   0    0   0    0   0   1   0    0   C2  C1           0.0
              2    0   0   0   0    0   0    0   0   1   0    0   C3  C1           0.0
              3    0   0   0   0    0   0    0   0   0   0    0   C3  C4           1.0),
             ('Expert5',
                 -vh  -h  -m  -l  -vl  na  +vl  +l  +m  +h  +vh From  To  no causality
              0    0   0   1   0    0   0    0   0   0   0    0   C1  C2           0.0
              1    0   0   0   0    0   0    0   0   1   0    0   C2  C1           0.0
              2    0   0   0   0    0   0    0   0   0   0    0   C3  C1           1.0
              3    0   0   0   0    0   0    0   0   0   0    0   C3  C4           1.0)])
  • Calculate the entropy of the expert ratings.
entropy = fcm.entropy(data)
Output[2]

Entropy
From	To	
C1	    C2	1.459148
C2	    C1	1.459148
C3	    C1	1.251629
        C4	1.459148
  • Build FCM connection matrix

Here we build FCM based on the qualitative input data using Larsen's implication method, family maximum aggregation method and the centroid defuzzification method.

weight_matrix = fcm.build(data=data, implication_method='Larsen')
Output[3]
        C2	        C1      	C4
C2	0.000000	0.610116	0.000000
C3	0.000000	0.541304	0.130328
C1	-0.722442	0.000000	0.000000

Run simulations on top of a defined FCM structure

In this example we will replicate the case presented in the fcm inference package in R by Dikopoulou & Papageorgiou

  • Instantiate and FcmSimulator class
sim = FcmSimulator()
  • Define the FCM structure
import pandas as pd

C1 = [0.0, 0.0, 0.6, 0.9, 0.0, 0.0, 0.0, 0.8]
C2 = [0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.5]
C3 = [0.0, 0.7, 0.0, 0.0, 0.9, 0.0, 0.4, 0.1]
C4 = [0.4, 0.0, 0.0, 0.0, 0.0, 0.9, 0.0, 0.0]
C5 = [0.0, 0.0, 0.0, 0.0, 0.0, -0.9, 0.0, 0.3]
C6 = [-0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
C7 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.4, 0.9]
C8 =[0.1, 0.0, 0.0, 0.0, 0.0, 0.1, 0.6, 0.0]

weight_matrix = pd.DataFrame([C1,C2, C3, C4, C5, C6, C7, C8], 
                    columns=['C1','C2','C3','C4','C5','C6','C7','C8'])
  • Define the initial state vector
init_state = {'C1': 1, 'C2': 1, 'C3': 0, 'C4': 0, 'C5': 0,
                    'C6': 0, 'C7': 0, 'C8': 0}
  • Simulate

Here we run a simulation on top of the defined FCM structure using the sigmoid transfer function and the modified Kosko's inference method. The simulation will run $50$ iterations and will stop if the absolute difference between the concept values between the simulation steps is $\leq 0.001$. The steepness parameter for the sigmoid function is set to $1$.

res_mK = sim.simulate(initial_state=init_state, weight_matrix=weight_matrix, transfer='sigmoid', inference='mKosko', thresh=0.001, iterations=50, l=1)
Output[4]

The values converged in the 7 state (e <= 0.001)
Output[5]

        C1	        C2          C3	        C4	        C5	        C6	        C7	        C8
0	1.000000	1.000000	0.000000	0.000000	0.000000	0.000000	0.000000	0.000000
1	0.750260	0.731059	0.645656	0.710950	0.500000	0.500000	0.549834	0.785835
2	0.738141	0.765490	0.749475	0.799982	0.746700	0.769999	0.838315	0.921361
3	0.730236	0.784168	0.767163	0.812191	0.805531	0.829309	0.898379	0.950172
4	0.727059	0.789378	0.769467	0.812967	0.816974	0.838759	0.908173	0.954927
5	0.726125	0.790510	0.769538	0.812650	0.818986	0.839860	0.909707	0.955666
6	0.725885	0.790706	0.769451	0.812473	0.819294	0.839901	0.909940	0.955774
plt.figure()
res_mK.plot(figsize=(15, 10))
plt.legend(bbox_to_anchor=(0.97, 0.94))

plt.xlabel('Simulation Steps')
plt.ylabel('Initial States')
plt.show()

png

Figure 1: The results of the FCM simulation.

Test Interventions on top of the defined FCMs

Here we will use the same initial state and the weight matrix defined in the previous example. Let's first create an instance of the FcmIntervention class. To do so we need to pass an fcmpy Simulator object.

inter = FcmIntervention(FcmSimulator)

Now we need to create a baseline for testing our interventions. We do so my using the FcmIntervention.initialize() method.

inter.initialize(initial_state=init_state, weight_matrix=weight_matrix, 
                        transfer='sigmoid', inference='mKosko', thresh=0.001, iterations=50, l=1)
Output[6]

The values converged in the 7 state (e <= 0.001)

This should already be familiar from the previous example. Here we just run a simulation on top of the a defined FCM (where no intervention exists) with a given vector of initial conditions. The baseline of comparison is the derived equilibrium states of the concepts in the FCM.

Now we can specify the interventions that we want to test. Let's consider three such hypothetical interventions we wish to test in our FCM. The first intervention targets concepts (nodes) C1 and C2. It negatively impacts concept C1 (-.3) while positively impacting the concept C2 (.5). We consider a case where the intervention has maximum effectiveness (1). The other two interventions follow the same logic but impact other nodes (see below).

inter.add_intervention('intervention_1', impact={'C1':-.3, 'C2' : .5}, effectiveness=1)
inter.add_intervention('intervention_2', impact={'C4':-.5}, effectiveness=1)
inter.add_intervention('intervention_3', impact={'C5':-1}, effectiveness=1)

Now we can use the FcmIntervention.test_intervention() method to test the intervention cases.

inter.test_intervention('intervention_1')
inter.test_intervention('intervention_2')
inter.test_intervention('intervention_3')
Output[6]

The values converged in the 6 state (e <= 0.001)
The values converged in the 6 state (e <= 0.001)
The values converged in the 6 state (e <= 0.001)

We can look at the results of the simulation runs of each intervention case as follows:

inter.test_results['intervention_1']
Output[7]

        C1	        C2	        C3	        C4	        C5	        C6	        C7	        C8	intervention
0	0.725885	0.790706	0.769451	0.812473	0.819294	0.839901	0.909940	0.955774	1.0
1	0.662298	0.861681	0.769410	0.812414	0.819328	0.839874	0.909973	0.955787	1.0
2	0.649547	0.869922	0.762564	0.803526	0.819327	0.839863	0.911132	0.955134	1.0
3	0.646000	0.870312	0.759929	0.800292	0.818413	0.838899	0.911143	0.954860	1.0
4	0.644962	0.870147	0.759059	0.799263	0.817925	0.838484	0.911052	0.954712	1.0
5	0.644651	0.870060	0.758786	0.798947	0.817735	0.838350	0.911004	0.954652	1.0

Now we can inspect the equilibrium states of the concepts in each intervention case.

inter.equilbriums
Output[8]

    baseline    intervention_1  intervention_2  intervention_3
C1  0.725885        0.644651        0.715704        0.723417
C2  0.790706        0.870060        0.790580        0.790708
C3  0.769451        0.758786        0.768132        0.769141
C4  0.812473        0.798947        0.699316        0.812073
C5  0.819294        0.817735        0.819160        0.563879
C6  0.839901        0.838350        0.823430        0.871834
C7  0.909940        0.911004        0.909917        0.909778
C8  0.955774        0.954652        0.955427        0.952199

Lastly, we can inspect the differences between the interventions in relative terms (i.e., % increase or decrease) compared to the baseline.

inter.comparison_table
Output[9]

    baseline    intervention_1  intervention_2  intervention_3
C1       0.0      -11.191083       -1.402511       -0.339981
C2       0.0       10.035821       -0.015968        0.000202
C3       0.0       -1.385998       -0.171325       -0.040271
C4       0.0       -1.664794      -13.927524       -0.049314
C5       0.0       -0.190233       -0.016379      -31.175022
C6       0.0       -0.184640       -1.960979        3.802010
C7       0.0        0.116873       -0.002543       -0.017806
C8       0.0       -0.117365       -0.036331       -0.374038

License

Please read LICENSE.txt in this directory.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fcmpy-0.0.2.tar.gz (49.8 kB view details)

Uploaded Source

Built Distribution

fcmpy-0.0.2-py3-none-any.whl (66.7 kB view details)

Uploaded Python 3

File details

Details for the file fcmpy-0.0.2.tar.gz.

File metadata

  • Download URL: fcmpy-0.0.2.tar.gz
  • Upload date:
  • Size: 49.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.6.0 importlib_metadata/3.10.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.8.8

File hashes

Hashes for fcmpy-0.0.2.tar.gz
Algorithm Hash digest
SHA256 dd36adb2a213124b9bcca5091a891604debb53efde637211cc089a7aad07cb94
MD5 e4be6867adda19fc17aa88164b34cfe2
BLAKE2b-256 2a01c4f0a88220ffc9c1dfde2a27363810ef4a2fe75f1e52ad4ded9ff28d952a

See more details on using hashes here.

File details

Details for the file fcmpy-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: fcmpy-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 66.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.6.0 importlib_metadata/3.10.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.59.0 CPython/3.8.8

File hashes

Hashes for fcmpy-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 fbe0339c933393729510531a0a258dee341f819d130aeb1d5a92a08c85a8df25
MD5 04055d0fedbc9f7f26da0dc706c0aa9b
BLAKE2b-256 e9e4f8726929bbcf84b970e619b6f195bf00c01a2a0199209064a8d99c494354

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page