R&D knowledge map again

15 분 소요

후 키워드 네트워크부터 다시 합시다.

  • 데이터 전처리부분은 거의 다 한 것 같아요. 맨 끝에 설명하겠습니다. 이제는 정말 단일한 데이터 집단을 모은게 아닐까 싶습니다.

  • 키워드 네트워크를 구성했을 때, 만든 네트워크에서 잘못된 노드 들이 있는 경우가 발견되었습니다. shape memory effect의 경우 약자가 똑같이 ‘sme’인 것을 알 수 있습니다. 따라서 다른 것보다 우선 이 부분을 제외하는 것이 필요한 것 같습니다.
  • 또한 앞서 공부한 몇 가지를 활용해서 더 잘 코딩을 할 수 있지 않을까? 생각도 해봅니다.

‘shape memory effect’ and ‘sme’

  • 우선 문제가 되는 부분은, ‘sme’ 는 ‘small and medium enterprise’일 수도 있고, ‘shape memory effect’일 수도 있다는 겁니다. 스코퍼스에서 데이터를 가져올 때, ‘sme’로 검색하여 양쪽의 데이터가 다 잡힌 것이죠. 이후에 word-embedding, structure equivalence 를 진행한다고 해도, 현재의 데이터 셋에 일관성이 없어서 이를 해결하는 것이 쉽지 않습니다.
    • 처음에는 이를 word-embedding이 해결해줄거야! 라고 생각했던 것 같은데 그렇지는 않습니다.
  • 따라서, 어떻게 해결할까 고민하다가, 가장 힘이 센(여기서는 일단 degree centrality라고 생각했습니다) 노드를 잘라나가면서 만약 가장 힘이 쎈 노드를 잘라내도 해당 그래프가 여전히 연결성이 있다면(nx.is_connected)라면 해당 그래프는 충분히 균일한 구조라고 평가할 수 있지 않을까? 라고 생각했습니다.

make graph dense

  • 코드는 대략 다음과 같습니다. nx.degree_centrality가 가장 높은 노드를 제외하면서 계속 진행하고, 제외해도 networkx의 전체 connection이 유지되는 경우에는 해당 네트워크는 아직 충분히 유효하다고 가정합니다. 즉 가장 강한 노드에만 연결되어 있는 노드는 모두 삭제한 그래프를 리턴합니다.
  • 이 코드가 잘 되었는지 판단하는 것은 좀 애매한데, 최소한 ‘shape memory effect’는 더이상 남아 있지 않습니다. 어느 정도의 키워드 필터링(특히, 분야의 일관성을 유지하기 위한) 은 되었다고 할 수 있을 것 같네요. 물론, 이를 이론적으로 증명하는 것은 좀 어려울 것 같긴 합니다만, 그래도 유사 공대생인 저에게는 이정도면 나름 유의미하다고 가정하겠습니다.
problem occurred
  • 아님. ‘shape memory effect’가 가지로 잘려나간 것이 아니라, 가장 높은 degree centrality를 가지고 있었기 때문에 잘려나간 것임. ‘superconducting magnetic energy storage’ 또한 여전히 살아남아있음. 그래서 마지막에 weight degree centrality가 높아서 제외했던 node를 다시 넣어주는 것이 적합하지 않다고 볼 수 있음.
  • weight degree centrality로 제외하는 것이 아니라, 다른 centrality를 사용해서 제외하는 것이 좋을까? 예를 들어서, closeness centrality가 betweenness centrality 가 높은 놈을 제외해주는 것이 더 자연스러운 것이 되지 않을까?
    • 어쨌든 전체 네트워크에서 전체 거리 상에서 가장 가까운 놈, 혹은 먼 놈들을 삭제해주는 것이 필요하지 않나 싶긴 한데, 현재 node가 10000개를 넘는 상황에서 closensess 를 계산해주는 것이 계산상 개 빡센게 맞음.
  • 내가 하려는 것은 결국, ‘shape memory effect’, ‘superconduction magnetic energ storage’를 사람이 발견해서 삭제하는 것이 아니라, 네트워크 분석에 의해서 합리적으로, 혹은 일종의 클러스터링으로 다른 분야라는 것이 명확하게 갈라질 수 있도록 하는 것.
  • 그런데, 이는 결국 클러스터링으로 쓰는 것이 적합할 것 같아요. 다시 생각해보면 서로 다른 분야라면 분명히 분리가 되어야 하니까요. 그리고, weight가 작은 것을 잘라 나간다고 반드시 ‘shape memory effect’가 잘려나간다고 할 수 없습니다. 결과에서 저 키워드를 중심으로 한 분야가 더 도미넌트할 수도 있는 거니까요.
def make_graph_dense(i_G):
    """graph에서 degree centrality가 가장 높은 node를 삭제해도, graph는 여전히 connected여야 함
    그럴때마다 잘려나가는 node는 모두 삭제함. 
    """
    tempG = i_G.copy()
    removed_nodes = set(i_G.nodes())
    for i in range(0, 100):
        top_deg_node = max(nx.degree_centrality(tempG).items(), key=lambda k: k[1])[0]
        removed_nodes.remove(top_deg_node)# 이 node는 마지막에 다시 넣어줘야 함.
        tempG.remove_nodes_from([top_deg_node])
        if nx.is_connected(tempG)==True:
            print("after try {}: remove top degree centrality doesn't matter".format(i))
            break
        else:
            tempG = max(nx.connected_component_subgraphs(tempG), key=lambda subG: len(subG.nodes()))
    """만약 100번을 넘어도 커넥션이 유지되지 않는다면 이를 메세지로 알려주는 것이 필요한데."""
    removed_nodes.difference_update(set(tempG.nodes()))
    rG = i_G.copy()
    rG.remove_nodes_from(removed_nodes)
    """단 이렇게 변형했을때, node의 weight attribute는 그대로 유지됨. 뭐 근데 별 의미없을 수 잇지만."""
    return rG
denseG = make_graph_dense(g)

use other method

  • 그래서, 다시 syntactical similarity를 확인하고, 단어를 정리해준 다음, clustering을 통해 아웃라이어를 발견해보려고 합니다.
  • difflib.SequenceMatcher를 활용하여 단어 간의 형태적 유사도를 측정하고, above_sim을 넘으며 above_node_w를 넘는 키워드들에 대한 변환 딕셔너리를 만들어주는 함수를 정의했습니다.
def syntactical_simialrity_dict(i_Series, above_node_w=10, above_sim=0.9):
    kwd_counter = itertools.chain.from_iterable(i_Series)
    kwd_count_dct = {w:c for w, c in Counter(kwd_counter).most_common() if c >= above_node_w}
    print("for computation efficienty, cut down node got below weight, remaining node is {}".format(len(kwd_count_dct)))
    """
    """
    kwd_changed_dct = {}
    for w1 in sorted(kwd_count_dct.keys()):
        for w2 in sorted(kwd_count_dct.keys()):
            if w1 < w2 and w1[0]==w2[0] and " " in w1 and " " in w2:
                """중복을 피하고, 처음 캐릭터가 같고, 해당 단어가 복합어일 것 
                """
                sim_v = difflib.SequenceMatcher(None,w1, w2).ratio()
                if sim_v >= above_sim:
                    if kwd_count_dct[w1] >= kwd_count_dct[w2]:
                        kwd_changed_dct[w2]=w1
                    else:
                        kwd_changed_dct[w1]=w2
    def make_non_transitive(input_dct):
        print('solving transivity')
        non_transvitiy_kwd_dict = {}
        for k, v in input_dct.items():
            while v in input_dct.keys():
                v = input_dct[v]
            non_transvitiy_kwd_dict[k] = v
        return non_transvitiy_kwd_dict
    return make_non_transitive(kwd_changed_dct)
"""
변환 dictionary를 series에 적용하는 함수
"""
def transform_by_dict(i_Series, input_dct):
    print('keyword set size: {}'.format(len(set(itertools.chain.from_iterable(i_Series)))))
    r_S = i_Series.apply(lambda ks: list(set([input_dct[k] if k in input_dct.keys() else k for k in ks])))
    print('keyword set size: {}'.format(len(set(itertools.chain.from_iterable(r_S)))))
    return r_S
  • 형태적인 분석으로도 키워드들이 많이 감소될 수 있을 줄 알고, 모든 키워드(node weight가 1인 경우까지 포함하여) 페어에 대해서 형태적인 유사도를 평가했으나, 생각보다 많이 감소되지 않았습니다. 계산시간은 30분 정도가 걸렸습니다. 겨우 1200개를 줄이자고 이런 짓을 하는 것이 의미가 있는가? 싶지만, 필요하면 해야죠…네, 아무튼 요런 형태로 형태를 변환합니다.
temp = basic_filter_Series(rawDF['Author Keywords'])
import time
start = time.time()
temp = transform_by_dict(temp, syntactical_simialrity_dict(temp, 1, 0.9))
end = time.time()
print(end-start)
for computation efficienty, cut down node got below weight, remaining node is 22883
solving transivity
keyword set size: 22883
keyword set size: 21644
1639.8741281032562

의미론적인 분석

  • 형태만으로 분석하는 것은 node weight가 1일 경우를 고려하여, 1000개 정도만 변환할 수 있습니다. 물론 이 정도도 나름 의미가 있기는 한데(이게 적합한지는 아직 체크를 못한 상황입니다), 이제 의미적으로 분석을 해볼게요.
  • adjacency matrix를 통해, 거리상으로 가까운 놈들을 확인해볼게요. 거리는 jaccardeuclidean을 사용했습니다. discrete space라서, jaccard가 더 적합할 것 같기는 한데 일단은 둘다 사용해봤습니다.

  • 해봤지만, 문제가 있습니다.
  • 의미론적인 분석에서 도출해낼 수 있는 가까운 키워드 페어를 예로 들자면 ‘Business Process modeling’과 ‘process modeling’ 나, ‘workflow mining’와 ‘process mining’등을 말할 수 있습니다만, 해당 분야의 전문가가 아니라면, 해당 semantic이 유의미하게 되었는지에 대해서 의문이 드는 것이 사실입니다. 다시 말해 그래, 얘들 간에 관계가 네트워크 구조적으로 가깝게 도출되었다는 것은 알겠는데, 그래서 ‘실제’로 얘네 의미가 같은가?는 어떻게 알 수 있을까요? 요 부분이 문제가 됩니다. 결국 domain expert가 ‘맞다’, ‘아니다’를 확인해주기 전까지는 이 방식만으로는 한계가 있는 것이죠.
temp = basic_filter_Series(rawDF['Author Keywords'])
temp = transform_by_dict(temp, syntactical_simialrity_dict(temp, 6, 0.9))
g = make_graph_from_series(temp)
"""node가 너무 많으면, 계산이 어려워져서 일정 이상의 weight를 가진 node만이 의미있는 node라고 가정했습니다. 
"""
g.remove_nodes_from( [n[0] for n in g.copy().nodes(data=True) if n[1]['weight']<=8] )

temp_df = pd.DataFrame(
    nx.adjacency_matrix(g).toarray(), index=[n for n in g.nodes()], columns=[n for n in g.nodes()]
)
print("the dimension of dataframe is {}".format(temp_df.shape[0]))

###

from scipy.spatial.distance import euclidean, jaccard
def make_close_pair_lst(adj_df, dist_func):
    r_lst = []
    for i in range(0, len(adj_df)-1):
        for j in range(i+1, len(adj_df)):
            r_lst.append(
                (adj_df.index[i], adj_df.index[j], dist_func(adj_df.iloc()[i], adj_df.iloc()[j]))
            )
    return sorted(r_lst, key=lambda x: x[2])
        
print("top 10 closest pair: euclidean space::")
"""euclidean distance의 경우는 표준화가 필요함. 
"""
for p in make_close_pair_lst(temp_df.apply(lambda col: (col)/(col.max())).fillna(0), euclidean)[:10]:
    print(p)
print("top 10 closest pair: jaccard space::")
for p in make_close_pair_lst(temp_df, jaccard)[:10]:
    print(p)
  • 실제로 아래 결과도 보면, ‘이게 된 건가, 아니면 안된건가 모르겠어요 솔직히’. 답이 없네요.
for computation efficienty, cut down node got below weight, remaining node is 1077
solving transivity
keyword set size: 22883
keyword set size: 22842
the dimension of dataframe is 639
top 10 closest pair: euclidean space::
('binary mixture', 'metallacarboranes', 0.0)
('binary mixture', 'flotation', 0.0)
('metallacarboranes', 'flotation', 0.0)
('binary mixture', 'carbapenemase', 0.0125)
('carbapenemase', 'metallacarboranes', 0.0125)
('carbapenemase', 'flotation', 0.0125)
('transient stability', 'battery', 0.29829779253058464)
('transient stability', 'carbapenemase', 0.3305048960211117)
('transient stability', 'binary mixture', 0.3307411923149668)
('transient stability', 'metallacarboranes', 0.3307411923149668)

top 10 closest pair: jaccard space::
  np.double(np.bitwise_or(u != 0, v != 0).sum()))
('exploration', 'exploitation', 0.29629629629629628)
('pecking order theory', 'trade off theory', 0.45454545454545453)
('microstructure', 'niti', 0.63636363636363635)
('transient stability', 'power quality', 0.66666666666666663)
('power quality', 'ac loss', 0.66666666666666663)
('superelasticity', 'actuator', 0.66666666666666663)
('superelasticity', 'nitinol', 0.66666666666666663)
('industrial symbiosis', 'circular economy', 0.66666666666666663)
('usability', 'requirements engineering', 0.69230769230769229)
('ahp', 'analytic hierarchy process', 0.70588235294117652)
  • 따라서, 뭔가 마음에 들지 않습니다. structural equivalence라고 해도, 저게 맞는지 아닌지를 모르는데 제가 뭘할 수 있나요 으앙. 우리가 찾는 파랑새나 황금섬 같은 건 없어여 시파.

for bipartite graph

  • 뭐 일단 시작한 김에 bipartite graph를 대상으로도 한번 수행해볼게요.
  • ‘Author Keywords’의 경우는 저자가 직접 입력하는 것이기 때문에, 해당 논문을 가장 잘 설명하는 키워드라고도 할 수 있지만, ‘주관적’일 수 있다는 한계를 가집니다. ‘index keywords’의 경우는 말 그대로 색인, 검색을 하기 위한 키워드기 때문에 일관적인 분류체계에 따라서 해당 콘텐츠를 분류하기 위해 사용하는 키워드라고 할 수 있습니다. 즉, 좀 더 일관적입니다.
  • 그렇다면 ‘Author Keywords’ ==> ‘Index Keywords’인 bipartite graph를 만들고 해당 네트워크에 대해서 similarity를 계산하면 좀 더 잘 되지 않을까요? 라고 생각했다고 합니다. 그러나 망했죠.
def make_bigraph_from_series(iS, jS):
    if len(iS)!=len(jS):
        print("different length of Series")
        return None
    rG = nx.Graph()
    edges = []
    def make_edges_from_bipartite_sets(setA, setB):
        if setA==[] or setB==[]:
            return []
        else:
            return [(n1, n2+"(i)") for n1 in setA for n2 in setB]
    for i in range(0, len(iS)):
        edges+=make_edges_from_bipartite_sets(iS.iloc()[i], jS.iloc()[i])
    print(len(edges))
    rG.add_edges_from([(e[0][0], e[0][1], {'weight':e[1]}) for e in Counter(edges).most_common()])
    print('is bipartite: {}'.format(nx.is_bipartite(rG)))
    print('is connected: {}'.format(nx.is_connected(rG)))
    return rG

temp_auth = basic_filter_Series(rawDF['Author Keywords'])
temp_auth = transform_by_dict(temp_auth, syntactical_simialrity_dict(temp_auth, 6, 0.9))
temp_auth = drop_lower_n(temp_auth, 10)

temp_ind = basic_filter_Series(rawDF['Index Keywords'])
temp_ind = transform_by_dict(temp_ind, syntactical_simialrity_dict(temp_ind, 6, 0.9))
temp_ind = drop_lower_n(temp_ind, 10)

biG = make_bigraph_from_series(temp_auth, temp_ind)
row_order = nx.bipartite.sets(biG)[0]
col_order = nx.bipartite.sets(biG)[1]
bi_df = pd.DataFrame(
    nx.bipartite.biadjacency_matrix(biG, row_order=row_order).toarray(),
    index = row_order, columns = col_order
)
print(bi_df.shape)

"""
print closest pairs 
"""
from scipy.spatial.distance import euclidean, jaccard
def make_close_pair_lst(adj_df, dist_func):
    r_lst = []
    for i in range(0, len(adj_df)-1):
        for j in range(i+1, len(adj_df)):
            r_lst.append(
                (adj_df.index[i], adj_df.index[j], dist_func(adj_df.iloc()[i], adj_df.iloc()[j]))
            )
    return sorted(r_lst, key=lambda x: x[2])
        
print("top 10 closest pair: euclidean space::")
"""euclidean distance의 경우는 표준화가 필요함. 
"""
for p in make_close_pair_lst(bi_df.apply(lambda col: (col)/(col.max())).fillna(0), euclidean)[:20]:
    print(p)
print("top 10 closest pair: jaccard space::")
for p in make_close_pair_lst(bi_df, jaccard)[:20]:
    print(p)
  • 아래는 bipartite graph 에서 구조적 등위성이 가까운 키워드 페어를 뽑은 것들입니다.
  • 음, 다시 생각해보니, 지금까지는 ‘데이터 필터링을 위해 구조적 등위성’을 사용했습니다. 구조적으로 비슷하면, 의미가 같아야 한다라고 가정한 것이죠. 음, 그러나, 이를 다르게 해석할 수도 있을 것 같습니다. ‘구조적으로 등위적인 관계에 있는 키워드들은 함께 연구 수행되는 일이 많다’ 라고 해석해도 되지 않을까? 하는 생각이 드네요.
top 10 closest pair: euclidean space::
('turkey', 'technological capability', 0.0)
('ipo', 'ambidexterity', 0.05857335354377807)
('turkey', 'ipo', 0.06330489210339914)
('ipo', 'technological capability', 0.06330489210339914)
('turkey', 'ambidexterity', 0.07471216944245575)
('technological capability', 'ambidexterity', 0.07471216944245575)
('ifrs for smes', 'ipo', 0.11898236645222844)
('ifrs for smes', 'ambidexterity', 0.12558563723121297)
('turkey', 'ifrs for smes', 0.12788143157459586)
('ifrs for smes', 'technological capability', 0.12788143157459586)
('management control', 'ipo', 0.1373770824684607)
('management control', 'ambidexterity', 0.14313424307182296)
('turkey', 'management control', 0.14515274645194254)
('management control', 'technological capability', 0.14515274645194254)
('ipo', 'asymmetric information', 0.14818941342657097)
('asymmetric information', 'ambidexterity', 0.15355874331293629)
('turkey', 'asymmetric information', 0.15542509213993358)
('technological capability', 'asymmetric information', 0.15542509213993358)
('france', 'ambidexterity', 0.15600418748894868)
('guanxi', 'ipo', 0.15622362679412727)

top 10 closest pair: jaccard space::
('turkey', 'technological capability', 0.0)
('foreign direct investment', 'japan', 0.33333333333333331)
('exploitation', 'exploration', 0.42105263157894735)
('rbv', 'emerging market', 0.47999999999999998)
('customer orientation', 'turkey', 0.5714285714285714)
('customer orientation', 'technological capability', 0.5714285714285714)
('new zealand', 'institutional theory', 0.59999999999999998)
('internationalization process', 'international entrepreneurship', 0.63636363636363635)
('customer satisfaction', 'performance management', 0.66666666666666663)
('united kingdom', 'performance management', 0.69230769230769229)
('financial constraint', 'ownership structure', 0.69999999999999996)
('guanxi', 'rbv', 0.70588235294117652)
('iran', 'scm', 0.7142857142857143)
('information', 'process', 0.71666666666666667)
('crowdfunding', 'bank loan', 0.72222222222222221)
('industrial symbiosis', 'circular economy', 0.72413793103448276)
('firm size', 'perception', 0.72549019607843135)
('crowdfunding', 'relationship lending', 0.72727272727272729)
('bank loan', 'relationship lending', 0.72727272727272729)
('ac loss', 'high temperature superconductor', 0.73076923076923073)

by clustering

  • 그렇다면, 단순히 단편적인 구조만 보는 것이 아니라 hierarchical clustering과 같은 방법으로 해주는 것은 어떨까요? 분명히 우리가 원하는 ‘small medium sized enterprise’ 이외의 분야들도 현재 포함되어 있기 때문에, 클러스터링을 통해서 이외에 속하는 부분을 싹 지워줄 수 있지 않을까, 라고 생각합니다.
  • 그래서 클러스터링을 했는데, 일반적으로 클러스터링은 해당 데이터 들을 가장 잘 설명해주는, 그리고 군집 간의 차이를 명확하게 설명할 수 있는 n을 찾는 것이 중요한데, 나는 일종의 outlier detection을 하기 위해 clustering 기법을 활용함.
  • 즉, 적합한 n_clusters를 찾는 것이 아니라, n_clusters를 늘려가면서 적합하지 않은 kwd cluster 뭉터기가 걸리기를 바라는 것.
  • 따라서, 여기서는 우선 cluster 갯수를 20개 정도로 하고 클러스터링한 다음 개별 클러스터별로 노드를 읽어본다. 대상 data는 Author KeywordsIndex Keywords를 사용해서 만든 bipartite graph의 adjancency matrix.
n_clusters = 20

AGGmodel = cluster.AgglomerativeClustering(n_clusters=n_clusters)

bi_cluster_df = pd.DataFrame(
    {'kwd': bi_df.index, 'cluster': AGGmodel.fit_predict(bi_df.apply(lambda col: [1 if x>=1 else 0 for x in col]))} 
)

for i in range(0, n_clusters):
    kwd_in_cluster = list(bi_cluster_df[bi_cluster_df['cluster']==i]['kwd'])
    if len(kwd_in_cluster)>=2 and len(kwd_in_cluster)<50:
        print("size of cluster {}: {}".format(i, len(kwd_in_cluster)))
        print(kwd_in_cluster)
  • 결과를 보면, cluster 12, 13, 18 이 다른 분야의 키워드들이라는 것을 알 수 있다. 따라서 해당 키워드들을 기존 dataframe에서 삭제한 다음 진행하는 것이 좋다.
size of cluster 0: 25
['manufacturing industry', 'strategy', 'manufacturing sme', 'china', 'supply chain management', 'productivity', 'sustainable development', 'survey', 'competitive advantage', 'malaysia', 'project management', 'competitiveness', 'developing country', 'enterprise', 'structural equation modelling', 'implementation', 'supply chain', 'ict', 'information and communication technology', 'performance', 'organizational performance', 'firm performance', 'corporate social responsibility', 'information technology', 'e commerce']
size of cluster 2: 2
['case study', 'knowledge management']
size of cluster 4: 4
['collaboration', 'network', 'manufacturing', 'open innovation']
size of cluster 6: 3
['barrier', 'energy efficiency', 'sustainability']
size of cluster 7: 2
['entrepreneurship', 'small business']
size of cluster 8: 26
['business intelligence', 'saas', 'e business', 'technology', 'web 2 0', 'technology adoption', 'big data', 'cloud', 'information system', 'cloud manufacturing', 'internet', 'entrepreneur', 'model', 'service', 'information security', 'ict adoption', 'open source', 'social media', 'trust', 'framework', 'business', 'software as a service', 'interoperability', 'open source software', 'cloud service', 'standard']
size of cluster 9: 5
['risk management', 'management', 'development', 'risk', 'risk assessment']
size of cluster 11: 2
['superconducting magnetic energy storage smes', 'optimization']
size of cluster 13: 9
['shape memory', 'phase transformation', 'shape memory polymer', 'superelasticity', 'martensitic transformation', 'microstructure', 'niti', 'shape memory alloy', 'shape memory effect']
size of cluster 16: 3
['adoption', 'enterprise resource planning', 'erp']
size of cluster 18: 16
['power quality', 'energy management', 'smart grid', 'energy storage', 'photovoltaic', 'power system', 'automatic generation control', 'transient stability', 'load frequency control', 'renewable energy', 'genetic algorithm', 'ac loss', 'high temperature superconductor', 'superconducting magnet', 'microgrid', 'power']
size of cluster 19: 16
['brazil', 'industrial symbiosis', 'environmental management', 'cleaner production', 'industry', 'eco design', 'industrial energy efficiency', 'public policy', 'decision making', 'driver', 'eco innovation', 'sustainable manufacturing', 'recycling', 'resource efficiency', 'life cycle assessment', 'circular economy']

이제 다시 또 코드를 정리합시다….

  • 매번 코드만 정리하는 것 같습니다만, 만약 제 컴퓨터가 좋아서 매번 코드를 마음대로 실행할 수 있다면 이런 문제점들은 사실 별로 없을 거에요. 현재 문제는 제 컴퓨터는 맥북에어고, 그래프를 연산하는데 시간이 아주 많이 걸립니다. 사실 전체 그래프 상에서 딱 한 번 등장하는 키워드들만 제외해도 큰 문제는 없죠.
  • 아무튼 다시 정리하겠습니다.

  • 정리하자면, 아래 코드는 결국 데이터 전처리에 불과한 내용입니다. 심지어 아직 다 한것도 아니에요.

한 것을 정리하자면…

  • 우선 series로부터 형태적으로 유사한 키워드 페어를 찾아서, (형태적으로 0.9이상 유사하고, composite word이며, 첫 글자가 같은 word들)에 대해서 변환을 해줍니다. 이를 series에 다시 적용해주고
  • 이제 의미적으로 유사한 것을 찾으려고, adj matrix나 bi adj matrix를 만들어서 vector간에 가까운 거리를 체크해봤는데, 여기서는 나름 유의미한 결과가 나오지 않았습니다. 그래서, 이거는 포기하고.
  • 그렇다면 clustering을 해보면 내가 필요 없다고 생각하는 ‘shape memory effect’등은 제외할 수 있지 않을까? 라고 생각했습니다. 그래서 좀 여러 클러스터로 쪼개 보니, 제가 볼 때 필요 없다고 판단되는 몇 가지 cluster들이 나오더군요. 그래서 그 아이들을 원래 series에 다시 넣어주었습니다.
  • 그래서, 이제야 일관적인 단일 분야의 ‘키워드 데이터’를 모은 것이 아닐까 싶어요
  • 낙관적으로 생각해서, 이제 여기서 structural equivalence를 다시 계산해보면, 이전보다 더 잘 나오지 않을까? 라고 혼자 생각해봅니다만….잘 모르겠군요 흠.
"""
주관적이지 않은 필터링, 아주 기본적인 filtering. 
split 부터 keyword list of list로 변환하여 리턴
"""
def basic_filter_Series(i_Series):
    r_Series = i_Series.copy().fillna("").apply(lambda s: s.strip().lower().split(";"))
    def replace_sp_chr(input_s):
        return "".join(map(lambda c: c if 'a'<=c and c<='z' else c if '0'<=c and c<='9'else " ", input_s)).strip()
    def remove_double_space(input_s):
        while "  " in input_s:
            input_s = input_s.replace("  ", " ")
        return input_s.strip()
    r_Series = r_Series.apply(
        lambda ks: list(map(
            lambda k: remove_double_space(replace_sp_chr(k)), ks)))
    
    all_kwd_set = set(itertools.chain.from_iterable(list(r_Series)))
    to_singular_dict = {}
    for kwd in all_kwd_set:
        singularized_kwd = singularize(kwd)
        if singularized_kwd !=kwd and singularized_kwd in all_kwd_set:
            to_singular_dict[kwd] = singularized_kwd
    """remove blank string"""
    r_Series = r_Series.apply(lambda ks:filter(lambda k: True if k!="" else False, ks))
    """singularize """
    r_Series = r_Series.apply(
        lambda ks: sorted(list(set(map(
            lambda k: to_singular_dict[k].strip() if k in to_singular_dict.keys() else k.strip(), ks
        )))))    
    return r_Series
"""
series로부터 그래프를 만들어주는 함수
"""
def make_graph_from_series(i_Series):
    rG = nx.Graph()
    rG.add_nodes_from(
        (n[0], {'weight':n[1]}) for n in Counter(itertools.chain.from_iterable(i_Series)).most_common())
    edges = []
    for x in i_Series:
        if len(x)!=0:
            edges += [(x[i], x[j]) for i in range(0, len(x)-1) for j in range(i+1, len(x))]
    rG.add_edges_from(
        [(e[0][0], e[0][1], {'weight':e[1]}) for e in Counter(edges).most_common()])
    return rG
"""
형태적으로 유사한 키워드를 찾아서 변환딕셔너리를 리턴. 
node의 weight가 above_node_w여야 하고, above_sim보다 유사도가 높아야 함
"""
def syntactical_simialrity_dict(i_Series, above_node_w=10, above_sim=0.9):
    kwd_counter = itertools.chain.from_iterable(i_Series)
    kwd_count_dct = {w:c for w, c in Counter(kwd_counter).most_common() if c >= above_node_w}
    print("for computation efficienty, cut down node got below weight, remaining node is {}".format(len(kwd_count_dct)))
    """
    """
    kwd_changed_dct = {}
    for w1 in sorted(kwd_count_dct.keys()):
        for w2 in sorted(kwd_count_dct.keys()):
            if w1 < w2 and w1[0]==w2[0] and " " in w1 and " " in w2:
                """중복을 피하고, 처음 캐릭터가 같고, 해당 단어가 복합어일 것 
                """
                sim_v = difflib.SequenceMatcher(None,w1, w2).ratio()
                if sim_v >= above_sim:
                    if kwd_count_dct[w1] >= kwd_count_dct[w2]:
                        kwd_changed_dct[w2]=w1
                    else:
                        kwd_changed_dct[w1]=w2
    """
    변환 딕셔너리를 non transitive하게, a==>b, b==>c 인 형태를 a==>c, b==>c 인 형태로 바꿔줌
    """
    def make_non_transitive(input_dct):
        print('solving transivity')
        non_transvitiy_kwd_dict = {}
        for k, v in input_dct.items():
            while v in input_dct.keys():
                v = input_dct[v]
            non_transvitiy_kwd_dict[k] = v
        return non_transvitiy_kwd_dict
    return make_non_transitive(kwd_changed_dct)

"""
입력받은 input_dct에 따라서 키워드를 변환하여 새로운 Series를 리턴
"""
def transform_by_dict(i_Series, input_dct):
    print()
    print("syntactically similar word를 변환해줍니다.")
    print('keyword set size: {}'.format(len(set(itertools.chain.from_iterable(i_Series)))))
    r_S = i_Series.apply(lambda ks: list(set([input_dct[k] if k in input_dct.keys() else k for k in ks])))
    print('keyword set size: {}'.format(len(set(itertools.chain.from_iterable(r_S)))))
    return r_S
"""
Series를 Counter로 변환한 다음, below_weight보다 큰 node만 남기고 Series를 리턴
"""
def drop_lower_n(i_Series, below_weight=10):
    print()
    print("series에서 weight가 {}와 같거나 작은 node를 삭제합니다.".format(below_weight))
    print('keyword set size: {}'.format(len(set(itertools.chain.from_iterable(i_Series)))))
    kwd_counter = {k:v for k, v in Counter(itertools.chain.from_iterable(i_Series)).most_common()}
    r_S = i_Series.apply(lambda ks: list(set([k for k in ks if kwd_counter[k] >= below_weight])))
    print('keyword set size: {}'.format(len(set(itertools.chain.from_iterable(r_S)))))
    return r_S
"""
불필요한 remove_node를 삭제함
"""
def remove_node_from_series(i_Series, remove_nodes):
    print("series에서 불필요한 node를 삭제합니다.")
    def func_remove_node(input_l):
        return [k for k in input_l if k not in remove_nodes]
    return i_Series.apply(func_remove_node)
"""
두 series로부터 bipartite graph를 만듬
"""
def make_bigraph_from_series(iS, jS):
    if len(iS)!=len(jS):
        print("different length of Series")
        return None
    rG = nx.Graph()
    edges = []
    def make_edges_from_bipartite_sets(setA, setB):
        if setA==[] or setB==[]:
            return []
        else:
            return [(n1, n2+"(i)") for n1 in setA for n2 in setB]
    for i in range(0, len(iS)):
        edges+=make_edges_from_bipartite_sets(iS.iloc()[i], jS.iloc()[i])
    rG.add_edges_from([(e[0][0], e[0][1], {'weight':e[1]}) for e in Counter(edges).most_common()])
    print('is bipartite: {}'.format(nx.is_bipartite(rG)))
    print('is connected: {}'.format(nx.is_connected(rG)))
    return rG


excel_path_and_filename = "../../../Downloads/SMEs_Scopus_2013-2017.xlsx"
rawDF = pd.read_excel(excel_path_and_filename)
df = rawDF[['Author Keywords', 'Year', 'Abstract', 'Index Keywords']].copy()

author_series = basic_filter_Series(df['Author Keywords'])
auth_syntactic_change_dict = syntactical_simialrity_dict(author_series, 10, 0.9)
author_series = transform_by_dict(author_series, auth_syntactic_change_dict)
author_series = drop_lower_n(author_series, 8)

ind_series = basic_filter_Series(df['Index Keywords'])
ind_syntactic_change_dict = syntactical_simialrity_dict(ind_series, 10, 0.9)
ind_series = transform_by_dict(ind_series, ind_syntactic_change_dict)
ind_series = drop_lower_n(ind_series, 15)

""" adj matrix
"""
"""
remove node because it is useless
이 부분은 clustering의 결과로 나온 cluster 중에서 분석하려고 하는 대상과 거리가 있는 키워드 묶음을 선정하여 기존 series에서 삭제하였다. 
"""
author_series = remove_node_from_series(author_series, 
['shape memory', 'phase transformation', 'thermomechanical treatment', 'sma', 'stimuli sensitive polymers', 'actuator', 'shape memory polymer', 'superelasticity', 'martensitic transformation', 'microstructure', 'niti', 'nitinol', 'shape memory alloy', 'shape memory effect']
+['fuzzy logic controller', 'power quality', 'energy management', 'battery', 'smart grid', 'energy storage', 'stability', 'photovoltaic', 'particle swarm optimization', 'power system', 'automatic generation control', 'transient stability', 'load frequency control', 'renewable energy', 'genetic algorithm', 'ac loss', 'high temperature superconductor', 'superconducting magnet', 'microgrid', 'power fluctuation', 'power']
+['superconducting magnetic energy storage smes', 'optimization']
)

authG = make_graph_from_series(author_series)
auth_adj_df = pd.DataFrame(
    nx.adjacency_matrix(authG).toarray(), index=[n for n in authG.nodes()], columns=[n for n in authG.nodes()]
)

"""bipartite graph and bipartite adjacency matrix 
"""
biG = make_bigraph_from_series(author_series, ind_series)
row_order = nx.bipartite.sets(biG)[0]
col_order = nx.bipartite.sets(biG)[1]
bi_df = pd.DataFrame(
    nx.bipartite.biadjacency_matrix(biG, row_order=row_order).toarray(),
    index = row_order, columns = col_order
)

""" adj matrix
"""
"""clustering
"""
n_clusters = 20

AGGmodel = cluster.AgglomerativeClustering(n_clusters=n_clusters)
bi_cluster_df = pd.DataFrame(
    # weight를 무시하고, 0, 1의 단일 연결로 하니까 더 잘되서 변환해서 거리를 잼
    {'kwd': bi_df.index, 'cluster': AGGmodel.fit_predict(bi_df.apply(lambda col: [1 if x>=1 else 0 for x in col]))} 
)

for i in range(0, n_clusters):
    kwd_in_cluster = list(bi_cluster_df[bi_cluster_df['cluster']==i]['kwd'])
    """너무 많지도 적지도 않은 클러스터를 확인
    """
    if len(kwd_in_cluster)>=2 and len(kwd_in_cluster)<50:
        print("size of cluster {}: {}".format(i, len(kwd_in_cluster)))
        print(kwd_in_cluster)

print("complete")

댓글남기기